模板参数typename和非typename之间的区别
difference between template parameters typename vs non typenames?
代码示例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;
默认参数(无论是默认函数参数还是默认模板参数)都不是函数签名的一部分。您只能定义一个具有给定签名的函数。
在代码示例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
,不能提供这样的值。
我想我得出了一个结论(也许任何人都可以验证或更新):
样品2:
当编译器遇到函数调用并试图将其与模板匹配时,它会找到两个参数为typename T
和typename T2
的模板(默认值无关紧要,这是正常的),这会产生歧义。
样品1:
当编译器取消函数调用时,它将尝试将其与两个模板中的一个匹配,其中一个模板将成功,因此它将具有typename T
和detail::enabler
,而另一个将没有为第二个模板参数定义值,因此在这种情况下将消除模糊性。
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- int(c) 和 c-'0' 之间的区别。C++
- 向量 <int> a {N, 0} 和 int arr a[N] = {0} 的时间复杂度有什么区别
- C++ - "!pointer"和"pointer == nullptr"的区别?
- C++ 使用 assign 函数的字符串与直接使用 '=' 更改值的字符串之间的区别
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 在 .h 文件中的类中声明静态变量和在.cpp文件中声明"global"变量有什么区别
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 在 const 函数中通过引用和指针返回之间的区别
- 我想知道长双倍和双倍之间的区别
- 返回常量对象引用 (getter) 和仅返回字符串有什么区别?
- 返回递归调用和仅递归调用的区别
- 为什么返回类型中需要typename?C++
- Qt:remove() 和 rmdir() 有什么区别
- 这 4 个 lambda 表达式之间有什么区别?
- 结构体 S { int align; } 之间的区别;(struct 关键字后的名称)和 struct { int al
- (double) 和 double() 之间的区别
- 将向量作为类>(值)<向量启动和向量<类>[值]有什么区别
- typedef 枚举和枚举类有什么区别?
- 模板参数typename和非typename之间的区别