为什么 std::enable_if 需要默认值?
Why the default value is needed for `std::enable_if`?
为什么我必须在此 std::enable_if
用法中使用默认值 (::type = 0
)?
我看到了示例,没有它也能正常工作。例如 https://foonathan.net/blog/2015/11/30/overload-resolution-4.html
#include<iostream>
#include<type_traits>
template <typename T,
typename std::enable_if<std::is_integral<T>::value, T>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff integral\n";
}
template <typename T,
typename std::enable_if<std::is_class<T>::value, T>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff class\n";
}
int main()
{
do_stuff(32);
return 0;
}
我收到错误消息:
temp.cpp:6:6: note: template argument deduction/substitution failed:
temp.cpp:18:13: note: couldn't deduce template parameter ‘<anonymous>’
应该推导为
template <template T, int>
void do_stuff(T t)
这是一个有效的代码。
我做错了什么? (gcc 版本 7.4.0)
我无法重现您的错误;无论如何,我在使用 class 调用 do_stuff()
时出错。举个例子
do_stuff(std::string{"abc"})
这是因为 do_stuff()
变为 do_stuff<std::string, std::string = 0>()
并且模板值不能是 std::string
类型(并且默认值不能为零)。
建议:重写你的函数,将 int
作为第二个位置值的类型
template <typename T, // .....................................VVV int, not T
typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff integral\n";
}
template <typename T, // ..................................VVV int, not T
typename std::enable_if<std::is_class<T>::value, int>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff class\n";
}
这样,调用 do_stuff(std::string{"abc"})
,您启用 do_stuff<std::string, int = 0>()
是可以接受的。
编译器清楚地告诉您问题出在哪里:在您的模板声明中,您指定了一个无法推导的额外模板 non-type 参数。您如何期望编译器为该 non-type 参数推断出正确的值?来自什么?
这正是上述使用 std::enable_if
的技术需要默认参数的原因。这是一个虚拟参数,因此默认参数值无关紧要(0
是自然选择)。
您可以将您的示例简化为一个
template <typename T, T x>
void foo(T t) {}
int main()
{
foo(42);
}
制作中
error: no matching function for call to 'foo(int)'
note: template argument deduction/substitution failed:
note: couldn't deduce template parameter 'x'
编译器可以推断出 T
是什么 (T == int
),但是编译器无法推断出 x
.
的参数
您的代码完全相同,除了您的第二个模板参数未命名(无需为虚拟参数命名)。
从你的评论来看,你似乎对代码中第二个参数声明中关键字 typename
的存在感到困惑,这让你相信第二个参数也是一个 类型参数。后者不是真的。
注意,在第二个参数的声明关键字typename
中使用了完全不同的作用。该关键字只是消除了
的语义歧义
std::enable_if<std::is_class<T>::value, T>::type
它告诉编译器嵌套名称 type
实际上代表 类型 的名称,而不是其他名称。 (您可以在此处阅读 typename
的用法:Why do we need typename here? and Where and why do I have to put the "template" and "typename" keywords?)
typename
的这种用法不会将模板的第二个参数变成 type 参数。您模板的第二个参数仍然是 non-type 参数。
这是另一个简化的示例,说明您的代码中发生的事情
struct S { typedef int nested_type; };
template <typename T, typename T::nested_type x>
void bar(T t)
{}
int main()
{
S s;
bar<S, 42>(s);
}
请注意,即使第二个参数的声明以 typename
开头,它仍然声明了一个 non-type 参数。
为什么我必须在此 std::enable_if
用法中使用默认值 (::type = 0
)?
我看到了示例,没有它也能正常工作。例如 https://foonathan.net/blog/2015/11/30/overload-resolution-4.html
#include<iostream>
#include<type_traits>
template <typename T,
typename std::enable_if<std::is_integral<T>::value, T>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff integral\n";
}
template <typename T,
typename std::enable_if<std::is_class<T>::value, T>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff class\n";
}
int main()
{
do_stuff(32);
return 0;
}
我收到错误消息:
temp.cpp:6:6: note: template argument deduction/substitution failed:
temp.cpp:18:13: note: couldn't deduce template parameter ‘<anonymous>’
应该推导为
template <template T, int>
void do_stuff(T t)
这是一个有效的代码。 我做错了什么? (gcc 版本 7.4.0)
我无法重现您的错误;无论如何,我在使用 class 调用 do_stuff()
时出错。举个例子
do_stuff(std::string{"abc"})
这是因为 do_stuff()
变为 do_stuff<std::string, std::string = 0>()
并且模板值不能是 std::string
类型(并且默认值不能为零)。
建议:重写你的函数,将 int
作为第二个位置值的类型
template <typename T, // .....................................VVV int, not T
typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff integral\n";
}
template <typename T, // ..................................VVV int, not T
typename std::enable_if<std::is_class<T>::value, int>::type = 0>
void do_stuff(T t) {
std::cout << "do_stuff class\n";
}
这样,调用 do_stuff(std::string{"abc"})
,您启用 do_stuff<std::string, int = 0>()
是可以接受的。
编译器清楚地告诉您问题出在哪里:在您的模板声明中,您指定了一个无法推导的额外模板 non-type 参数。您如何期望编译器为该 non-type 参数推断出正确的值?来自什么?
这正是上述使用 std::enable_if
的技术需要默认参数的原因。这是一个虚拟参数,因此默认参数值无关紧要(0
是自然选择)。
您可以将您的示例简化为一个
template <typename T, T x>
void foo(T t) {}
int main()
{
foo(42);
}
制作中
error: no matching function for call to 'foo(int)'
note: template argument deduction/substitution failed:
note: couldn't deduce template parameter 'x'
编译器可以推断出 T
是什么 (T == int
),但是编译器无法推断出 x
.
您的代码完全相同,除了您的第二个模板参数未命名(无需为虚拟参数命名)。
从你的评论来看,你似乎对代码中第二个参数声明中关键字 typename
的存在感到困惑,这让你相信第二个参数也是一个 类型参数。后者不是真的。
注意,在第二个参数的声明关键字typename
中使用了完全不同的作用。该关键字只是消除了
std::enable_if<std::is_class<T>::value, T>::type
它告诉编译器嵌套名称 type
实际上代表 类型 的名称,而不是其他名称。 (您可以在此处阅读 typename
的用法:Why do we need typename here? and Where and why do I have to put the "template" and "typename" keywords?)
typename
的这种用法不会将模板的第二个参数变成 type 参数。您模板的第二个参数仍然是 non-type 参数。
这是另一个简化的示例,说明您的代码中发生的事情
struct S { typedef int nested_type; };
template <typename T, typename T::nested_type x>
void bar(T t)
{}
int main()
{
S s;
bar<S, 42>(s);
}
请注意,即使第二个参数的声明以 typename
开头,它仍然声明了一个 non-type 参数。