代码示例1:
namespace detail {
enum enabler { dummy };
}
class foo {
public:
template <typename T,
typename std::enable_if<!std::is_integral<T>::value,
detail::enabler>::type = detail::enabler::dummy>
void func(T t) {
std::cout << "other" << std::endl;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value,
detail::enabler>::type = detail::enabler::dummy>
void func(T t) {
std::cout << "integral" << std::endl;
}
};
代码示例2:
namespace detail {
enum enabler { dummy };
}
class foo {
public:
template <typename T,
typename T2 = typename std::enable_if<!std::is_integral<T>::value, detail::enabler>::type>
void func(T t) {
std::cout << "other" << std::endl;
}
template <typename T,
typename T2 = typename std::enable_if<std::is_integral<T>::value, detail::enabler>::type>
void func(T t) {
std::cout << "integral" << std::endl;
}
};
我理解为什么样本2不能编译。基本上因为两个模板函数彼此相似(它们甚至具有相同的模板参数T
,T2
)。
我不明白为什么样本1编译!我发现这是同样的问题。基本上,不是将第二个模板参数作为typename
,而是这次enum
而不是typename
。
有人可以解释一下吗?
此外,我执行了以下代码,它返回true,这更令人困惑! (理所当然,但是样本1编译会让人感到困惑)
std::cout
<< std::boolalpha
<< std::is_same<std::enable_if<std::is_integral<int>::value, int>::type,
std::enable_if<!std::is_integral<float>::value, int>::type>::value
<< std::endl;
答案 0 :(得分:8)
默认参数(无论是默认函数参数还是默认模板参数)都不是函数签名的一部分。您只能使用给定签名定义一个函数。
在代码示例1中,如果我们删除参数的名称和所有默认值,我们有两个函数:
template <typename T, std::enable_if_t<!std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }
template <typename T, std::enable_if_t<std::is_integral<T>::value, detail::enabler>>
void func(T ) { ... }
这是两个不同的签名 - 第二个模板参数在两个函数中都有不同的类型 - 因此从这个角度看它是有效的。
现在,当我们实际调用它时会发生什么。如果T
是整数类型,则在两种情况下都会替换第二个参数:
template <typename T, ????> void func(T ) { ... }
template <typename T, detail::enabler = dummy> void func(T ) { ... }
在第一个函数中,表达式typename std::enable_if<false, detail::enabler>::type
是不正确的。该类型没有type
typedef。通常,编写不正确的代码是一个硬编译器错误。但是,由于一个名为SFINAE的规则(替换失败不是错误),在模板替换的直接上下文中出现的格式错误的代码不是错误 - 它只会导致从考虑集中删除函数/类模板特化。所以我们最终得到一个有效的:
template <typename T, detail::enabler = dummy> void func(T ) { ... }
被调用。
但是,在代码示例2中,我们有以下两个函数:
template <typename T, typename>
void func(T ) { ... }
template <typename T, typename>
void func(T ) { ... }
那些是一样的!我们两次定义相同的函数 - 这是不允许的,因此错误。出于同样的原因,这是一个错误:
int foo(int x = 1) { return x; }
int foo(int x = 2) { return x; }
是一个错误。
这就是为什么Xeo使用(不可构造的)枚举来启用if - 它使得更容易编写不相交的函数模板特化,因为你可以使用枚举,但是你不能用类型来做。
请注意,您可以类似地使用int
:
template <class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
void func(T ) { ... }
但是这将允许邪恶的用户为该参数提供一个值(在这个特定的上下文中它不重要,但在其他情况下它可能不重要)。使用detail::enabler
时,不能提供此类值。
答案 1 :(得分:2)
我想我得出了一个结论(也许任何人都可以验证或更新):
在样本2中:
当编译器遇到函数调用并尝试将其与模板匹配时,它会找到两个参数为typename T
和typename T2
的模板(默认值无关紧要,这是正常的),这会创建含糊不清。
在样本1中:
当编译器发出函数调用时,它会尝试将它与两个模板中的一个匹配,其中一个模板将成功,因此它将具有typename T
和detail::enabler
而另一个将没有值为第二个模板参数定义,因此在这种情况下将消除歧义。