SFINAE 在类型和非类型模板参数的情况下工作方式不同
Posted
技术标签:
【中文标题】SFINAE 在类型和非类型模板参数的情况下工作方式不同【英文标题】:SFINAE works differently in cases of type and non-type template parameters 【发布时间】:2016-07-29 16:22:29 【问题描述】:为什么这段代码有效:
template<
typename T,
std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr>
void Add(T)
template<
typename T,
std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr>
void Add(T)
并且可以正确区分这两个调用:
Add(1);
Add(1.0);
如果编译以下代码会导致重新定义 Add() 错误?
template<
typename T,
typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T)
template<
typename T,
typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T)
所以如果模板参数是类型,那么我们重新定义函数,如果是非类型,那么一切正常。
【问题讨论】:
想象一下你编写了两个函数“重载”,如下所示:void Add(int = 0);
和 void Add(int = 1)
。它们真的不同吗?
SFINAE for class member function (one compiles the other not)的可能重复
【参考方案1】:
SFINAE 是关于替代的。所以让我们代替吧!
template<
typename T,
std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr>
void Add(T)
template<
typename T,
std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr>
void Add(T)
变成:
template<
class T=int,
int* = nullptr>
void Add(int)
template<
class T=int,
Substitution failure* = nullptr>
void Add(int)
template<
class T=double,
Substitution failure* = nullptr>
void Add(double)
template<
class T=double
double* = nullptr>
void Add(double)
删除我们得到的失败:
template<
class T=int,
int* = nullptr>
void Add(int)
template<
class T=double
double* = nullptr>
void Add(double)
现在移除模板参数值:
template<
class T,
int*>
void Add(T)
template<
class T
double*>
void Add(T)
这些是不同的模板。
现在搞砸了:
template<
typename T,
typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T)
template<
typename T,
typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T)
变成:
template<
typename T=int,
typename =int>
void Add(int)
template<
typename int,
typename = Substitution failure >
void Add(int)
template<
typename T=double,
typename = Substitution failure >
void Add(double)
template<
typename T=double,
typename = double>
void Add(double)
删除失败:
template<
typename T=int,
typename =int>
void Add(int)
template<
typename T=double,
typename = double>
void Add(double)
现在模板参数值:
template<
typename T,
typename>
void Add(T)
template<
typename T,
typename>
void Add(T)
这些是相同的模板签名。这是不允许的,会产生错误。
为什么会有这样的规则?超出了这个答案的范围。我只是在演示这两种情况的不同之处,并断言标准对它们的处理方式不同。
当您使用像上面这样的非类型模板参数时,您会更改模板签名而不仅仅是模板参数值。当您使用上述类型模板参数时,您只需更改模板参数值。
【讨论】:
【参考方案2】:这里的问题是add()
的模板签名是一样的:一个函数模板接受两个参数类型。
所以当你写的时候:
template<
typename T,
typename = std::enable_if_t<std::is_same<T, int>::value, T>>
void Add(T)
没关系,但是当你写的时候:
template<
typename T,
typename = std::enable_if_t<!std::is_same<T, int>::value, T>>
void Add(T)
您正在重新定义第一个 add()
模板,只是这次您为第二个模板参数指定了不同的默认类型:最后,您为 add()
定义了具有完全相同签名的重载,因此出现了错误。
如果您想要像您的问题建议的那样有多个实现,您应该使用std::enable_if_t
作为模板的返回参数,或者以与第一个示例相同的方式使用它。所以你的初始代码变成:
template<typename T>
std::enable_if_t<std::is_same<T, int>::value> Add(T)
template<typename T>
std::enable_if_t<!std::is_same<T, int>::value> Add(T)
Coliru 上的工作示例
在上面的代码中,如果T == int
,第二个签名无效并触发SFINAE。
NB:假设您想要 N 个实现。您可以使用与上述相同的技巧,但您需要确保 N 中只有一个布尔值为真,而剩下的 N-1 为假,否则您将得到完全相同的错误!
【讨论】:
@Constructor 是的,我忘了在第二个模板上添加!
。固定!【参考方案3】:
我认为问题在于,即使默认模板参数无法通过为其指定不同的值进行编译,您也可以使用该函数。想想如果在 add 调用中指定两个模板参数会发生什么。
【讨论】:
【参考方案4】:我将尝试首先给出一个不使用模板但使用默认参数的示例。以下示例与您的第二个示例失败的原因相当,尽管它并不表示模板重载解析的内部工作原理。
您有两个这样声明的函数:
void foo(int _arg1, int _arg2 = 3);
还有
void foo(int _arg1, int _arg2 = 4);
希望您意识到这将无法编译,它们永远不会成为区分使用默认参数对foo
的两次调用的方法。 完全模棱两可,编译器怎么会知道选择哪个默认值?你可能想知道为什么我用这个例子,毕竟第一个例子中的模板不应该推导出不同的类型吗?对此的简短回答是否定的,那是因为第二个示例中两个模板的“签名”:
template<
typename T,
typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T)
template<
typename T,
typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T)
...具有完全相同的签名,即:
template<typename T,typename>
void Add(T);
和(分别)
template <typename T, typename>
void Add(T);
现在您应该开始了解我给出的非模板示例与您提供的失败示例之间的相似性。
【讨论】:
【参考方案5】:SFINAE 不会传播到类型或值的默认值。此技术仅使用函数参数和结果的类型。
【讨论】:
以上是关于SFINAE 在类型和非类型模板参数的情况下工作方式不同的主要内容,如果未能解决你的问题,请参考以下文章