具有模板参数推导和默认模板参数的模板变量
Template variables with template argument deduction and default template parameters
对感到惊讶(并感到困惑)我自己尝试了上述问题在标准中找到的示例:
template <typename T, typename U = int> struct S;
template <typename T = int, typename U> struct S
{ void f() { std::cout << __PRETTY_FUNCTION__ << '\n'; } };
int main()
{
S s; s.f();
return 0;
}
上面的代码打印 void S<int, int>::f() [T = int, U = int]
compiled with gcc HEAD 8.0.1 201803 but fails to compile with clang HEAD 7.0.0 除非在实例化期间使用尖括号:
S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
S<> t; t.f(); // Correct
撇开这个问题不谈,我检查了其他 模板风格 以了解此特定行为,代码以非常不规则的方式被接受或拒绝:
Template function
template <typename T, typename U = int> void function();
template <typename T = int, typename U> void function()
{ std::cout << __PRETTY_FUNCTION__ << '\n'; }
int main()
{
/* Rejected by GCC: no matching function for call to 'function()'
template argument deduction/substitution failed:
couldn't deduce template parameter 'T'
same error with function<>()
CLang compiles without issues */
function(); // CLang prints 'void function() [T = int, U = int]'
return 0;
}
Template variable
template <typename T, typename U = int> int variable;
template <typename T = int, typename U> int variable = 0;
int main()
{
/* GCC complains about wrong number of template arguments (0, should be at least 1)
while CLang complains about redefinition of 'variable' */
std::cout << variable<> << '\n';
return 0;
}
Template alias
template <typename T, typename U = int> using alias = int;
template <typename T = int, typename U> using alias = int;
int main()
{
/* GCC complains about redefinition of 'alias'
while CLang compiles just fine. */
alias<> v = 0;
std::cout << v << '\n';
return 0;
}
关于此功能的 standards text 并未区分不同的模板类型,因此我认为它们的行为应该相同。
但是,模板变量大小写是两个编译器都拒绝的,所以我对模板变量选项有些怀疑。对我来说,CLang 拒绝抱怨重新定义的模板变量是正确的,而 GCC 是错误的,因为 错误的原因 拒绝了代码,但这种推理不符合标准所说的在 [temp.param]/10.
那么对于模板变量的情况我应该期待什么?:
- 代码因重新定义而被拒绝(CLang 是对的)。
- 已接受代码,合并两个模板定义(GCC 和 CLang 都是错误的)。
免责声明:以下内容在 C++14 的上下文中有效。对于 C++17,两个编译器都是错误的。 请参阅 Barry 的另一个答案。
查看细节我发现 Clang 在这里是正确的,而 GCC 是混淆的。
第一种情况,class模板(不像函数模板)确实需要<>
.
第二种情况,函数模板,Clang 对待第一种情况完全一样,没有语法要求使用 <>
来指示使用模板。这在 C++ 中适用于所有上下文中的函数模板。
第三种情况:从变量上看,Clang是对的,GCC是糊涂的。如果您使用 extern
重新声明变量,Clang 会接受它。
template <typename T, typename U = int> int variable = 0;
template <typename T = int, typename U> extern int variable;
int main()
{
// accepted by clang++-3.9 -std=c++14
std::cout << variable<> << '\n';
return 0;
}
所以它再次与标准和以前的情况保持一致。没有extern
这是重定义,是禁止的。
第四种情况,using
模板。 Clang 再次表现一致。我使用 typeid 来确保别名确实是 int:
template <typename T, typename U = int> using alias = int;
template <typename T = int, typename U> using alias = int;
int main()
{
alias<> v = 0;
std::cout << v << '\n';
std::cout << typeid(v).name() << '\n';
return 0;
}
然后
$ ./a.out | c++filt -t
产出
0
int
所以 Clang 确实不区分哪种模板是 re-declared,如 standard.
中所述
对于 class 模板参数推导,这是一个 clang 错误。来自 [temp.param]/14:
The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are ([dcl.fct.default]). [ Example:
template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;
is equivalent to
template<class T1 = int, class T2 = int> class A;
— end example ]
当你写 S s
时,两个模板参数都是默认的,所以默认构造函数的 rewrite 是:
template <typename T=int, typename U=int>
S<T, U> __f();
哪个是可行的,应该推导出 S<int, int>
。
对于函数,如果可以推导出所有模板参数,则无需指定<>
调用函数模板。这是 [temp.arg.explicit]/3:
If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <>
itself may also be omitted.
请注意,这适用于扣除。别名模板或变量模板没有扣除。因此,您不能省略 <>
。这是 [temp.arg]/4:
When template argument packs or default template-arguments are used, a template-argument list can be empty. In that case the empty <>
brackets shall still be used as the template-argument-list.
对
template <typename T, typename U = int> struct S;
template <typename T = int, typename U> struct S
{ void f() { std::cout << __PRETTY_FUNCTION__ << '\n'; } };
int main()
{
S s; s.f();
return 0;
}
上面的代码打印 void S<int, int>::f() [T = int, U = int]
compiled with gcc HEAD 8.0.1 201803 but fails to compile with clang HEAD 7.0.0 除非在实例化期间使用尖括号:
S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
S<> t; t.f(); // Correct
撇开这个问题不谈,我检查了其他 模板风格 以了解此特定行为,代码以非常不规则的方式被接受或拒绝:
Template function
template <typename T, typename U = int> void function();
template <typename T = int, typename U> void function()
{ std::cout << __PRETTY_FUNCTION__ << '\n'; }
int main()
{
/* Rejected by GCC: no matching function for call to 'function()'
template argument deduction/substitution failed:
couldn't deduce template parameter 'T'
same error with function<>()
CLang compiles without issues */
function(); // CLang prints 'void function() [T = int, U = int]'
return 0;
}
Template variable
template <typename T, typename U = int> int variable;
template <typename T = int, typename U> int variable = 0;
int main()
{
/* GCC complains about wrong number of template arguments (0, should be at least 1)
while CLang complains about redefinition of 'variable' */
std::cout << variable<> << '\n';
return 0;
}
Template alias
template <typename T, typename U = int> using alias = int;
template <typename T = int, typename U> using alias = int;
int main()
{
/* GCC complains about redefinition of 'alias'
while CLang compiles just fine. */
alias<> v = 0;
std::cout << v << '\n';
return 0;
}
关于此功能的 standards text 并未区分不同的模板类型,因此我认为它们的行为应该相同。
但是,模板变量大小写是两个编译器都拒绝的,所以我对模板变量选项有些怀疑。对我来说,CLang 拒绝抱怨重新定义的模板变量是正确的,而 GCC 是错误的,因为 错误的原因 拒绝了代码,但这种推理不符合标准所说的在 [temp.param]/10.
那么对于模板变量的情况我应该期待什么?:
- 代码因重新定义而被拒绝(CLang 是对的)。
- 已接受代码,合并两个模板定义(GCC 和 CLang 都是错误的)。
免责声明:以下内容在 C++14 的上下文中有效。对于 C++17,两个编译器都是错误的。 请参阅 Barry 的另一个答案。
查看细节我发现 Clang 在这里是正确的,而 GCC 是混淆的。
第一种情况,class模板(不像函数模板)确实需要
<>
.第二种情况,函数模板,Clang 对待第一种情况完全一样,没有语法要求使用
<>
来指示使用模板。这在 C++ 中适用于所有上下文中的函数模板。第三种情况:从变量上看,Clang是对的,GCC是糊涂的。如果您使用
extern
重新声明变量,Clang 会接受它。template <typename T, typename U = int> int variable = 0; template <typename T = int, typename U> extern int variable; int main() { // accepted by clang++-3.9 -std=c++14 std::cout << variable<> << '\n'; return 0; }
所以它再次与标准和以前的情况保持一致。没有
extern
这是重定义,是禁止的。第四种情况,
using
模板。 Clang 再次表现一致。我使用 typeid 来确保别名确实是 int:template <typename T, typename U = int> using alias = int; template <typename T = int, typename U> using alias = int; int main() { alias<> v = 0; std::cout << v << '\n'; std::cout << typeid(v).name() << '\n'; return 0; }
然后
$ ./a.out | c++filt -t
产出
0 int
所以 Clang 确实不区分哪种模板是 re-declared,如 standard.
中所述对于 class 模板参数推导,这是一个 clang 错误。来自 [temp.param]/14:
The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are ([dcl.fct.default]). [ Example:
template<class T1, class T2 = int> class A; template<class T1 = int, class T2> class A;
is equivalent to
template<class T1 = int, class T2 = int> class A;
— end example ]
当你写 S s
时,两个模板参数都是默认的,所以默认构造函数的 rewrite 是:
template <typename T=int, typename U=int>
S<T, U> __f();
哪个是可行的,应该推导出 S<int, int>
。
对于函数,如果可以推导出所有模板参数,则无需指定<>
调用函数模板。这是 [temp.arg.explicit]/3:
If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list
<>
itself may also be omitted.
请注意,这适用于扣除。别名模板或变量模板没有扣除。因此,您不能省略 <>
。这是 [temp.arg]/4:
When template argument packs or default template-arguments are used, a template-argument list can be empty. In that case the empty
<>
brackets shall still be used as the template-argument-list.