模板参数类型 `void` 与显式使用 `void`

Template parameter type `void` vs explicit use of `void`

在下面的代码中,为什么模板函数 foo 的显式扩展编译失败,而 bar 的扩展编译成功?直播 link - https://godbolt.org/z/o8Ea49KEb

template <typename T1, typename T2>
T1 foo(T2) { T2(42); return T1{}; };

template <typename T1, typename T2>
T1 bar(void) { T2(42); return T1{}; };

int main()
{
    foo<int, void>();    // fails

    bar<int, void>();    // works
}

请注意,模板参数 T2 用于两个函数的主体,唯一的区别是 bar 的函数参数已被手动替换。

这个问题是在阅读 并试图简化问题后受到启发的。

参数列表 (void) 与空参数列表 () 相同的规则在 C++ 标准中找到 [dcl.fct]/4:

A parameter list consisting of a single unnamed parameter of non-dependent type void is equivalent to an empty parameter list. Except for this special case, a parameter shall not have type cv void.

这个问题的重点是“non-dependent类型”。模板参数T是type-dependent,所以不会触发规则。

我认为标准有这条规则,因为如果一个通常采用一个参数的模板函数突然变成一个 zero-parameter 函数,如果实例化碰巧使该参数类型为 void。有了这个规则,我们就知道当一个模板被声明为一个参数时,它真的只有一个参数。

考虑以下几点:

template<typename T>
void foo(T) {}

void bar(void) {}

您假设 foo<void>bar(void) 是等价的,但它们不是。 foo<T> 总是需要一个参数。所以foo<void>等价于一个函数baz(void x)。这不会编译,因为您不能拥有 void 类型的值。这与您收到的错误消息相同。

在函数签名中void bar(void)括号中的空表示不同的东西:一个没有参数的函数。这是 C 的残余,不推荐。

函数 bar<void>(T) baz(void x) 不能存在,因为它们都需要类型为 void 的值,而该值不存在。

模板函数的解决方案是为模板实例化非法的情况提供重载或特化。

template<typename T>
void foo(T) {} // one argument

void foo() {}  // no arguments -> different signatures -> overload

template<typename T> 
void bar() {}  // no arguments

template<>
void bar<void> {} // still no arguments -> same signature -> specialisation