C++ SFINAE在类型和非类型模板参数的情况下的工作方式不同

C++ SFINAE在类型和非类型模板参数的情况下的工作方式不同,c++,templates,c++11,overloading,sfinae,C++,Templates,C++11,Overloading,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

此代码为何有效:

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()错误

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) {}
模板<
类型名T,
typename=typename std::enable_if::type>
void Add(T){}
模板<
类型名T,
typename=typename std::enable_if::value,T>::type>
void Add(T){}

因此,如果模板参数是type,那么我们对函数进行了重新定义,如果它是非type,那么一切都正常。

我认为问题在于,即使默认模板参数没有通过为其指定不同的值来编译,您也可以使用该函数。想象一下,如果在要添加的调用中指定了两个模板参数,会发生什么情况。

SFINAE不会扩展为类型或值的默认值。此技术中只使用函数参数和结果的类型。

这里的问题是
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) {}
工作实例

在上面的代码中,如果
T==int
,则第二个签名无效,并触发SFINAE


注意:假设您想要N个实现。您可以使用与上面相同的技巧,但是您需要确保N中只有一个布尔值为真,剩下的N-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) {}
模板<
类型名T,
std::如果\u t*=nullptr>
void Add(T){}
模板<
类型名T,
std::如果\u t::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<
  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) {}
模板<
类T=int,
int*=nullptr>
void Add(int){}
模板<
类T=int,
替换失败*=nullptr>
无效添加(int){
模板<
T类=双,
替换失败*=nullptr>
空加(双){}
模板<
T类=双
双精度*=空PTR>
空加(双){}
删除我们得到的故障:

template<
  class T=int, 
  int* = nullptr>
void Add(int) {}
template<
  class T=double
  double* = nullptr>
void Add(double) {}
模板<
类T=int,
int*=nullptr>
void Add(int){}
模板<
T类=双
双精度*=空PTR>
空加(双){}
现在删除模板参数值:

template<
  typename T, 
  typename>
void Add(T) {}
template<
  typename T, 
  typename>
void Add(T) {}
模板<
T类,
int*>
void Add(T){}
模板<
T类
双精度*>
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) {}
模板<
类型名T,
typename=typename std::enable_if::type>
void Add(T){}
模板<
类型名T,
typename=typename std::enable_if::value,T>::type>
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<
  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) {}
模板<
类型名T=int,
typename=int>
void Add(int){}
模板<
typename int,
typename=替换失败>
void Add(int){}
模板<
typename T=double,
typename=替换失败>
空加(双){}
模板<
typename T=double,
typename=double>
空加(双){}
删除故障:

template<
  typename T=int, 
  typename =int>
void Add(int) {}
template<
  typename T=double, 
  typename = double>
void Add(double) {}
模板<
类型名T=int,
typename=int>
void Add(int){}
模板<
typename T=double,
typename=double>
空加(双){}
现在是模板参数值:

template<
  typename T, 
  typename>
void Add(T) {}
template<
  typename T, 
  typename>
void Add(T) {}
模板<
类型名T,
typename>
void Add(T){}
模板<
类型名T,
typename>
void Add(T){}
这些是相同的模板签名。这是不允许的,生成错误

为什么会有这样一个规则?超出了这个答案的范围。我只是演示这两种情况是如何不同的,并断言标准对待它们是不同的


当您使用如上所述的非类型模板参数时,您不仅会更改模板参数值,还会更改模板签名。当您使用如上所述的类型模板参数时,您只会更改模板参数值。

我将尝试首先给出一个不使用模板但带有默认参数的示例。下面的g示例与您的第二个示例失败的原因类似,尽管它并不表示模板重载解析的内部工作方式

您有两个这样声明的函数:

void foo(int _arg1, int _arg2 = 3);

希望您能意识到这将无法编译,它们永远不会成为用默认参数区分对
foo
的两个调用的方法。这完全是不明确的,编译器如何知道选择哪个默认值?您可能想知道我为什么要用这个示例,毕竟在第一个示例中模板不应该吗le推断不同的类型?简短的回答是否定的,这是因为在第二个示例中,两个模板的“签名”:

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) {}
模板<
类型名T,
typename=typename std::enable_if::type>
void Add(T){}
模板<
类型名T,
typename=typename std::enable_if::value,T>::type>
void Add(T){}
…具有完全相同的签名,即:

template<typename T,typename>
void Add(T);
模板
无效添加(T);
及(分别)

模板
无效添加(T);

现在,您应该开始理解我给出的非模板示例与您提供的失败示例之间的相似性。

想象您编写了两个函数“重载”,如下所示:
void Add(int=0);
void Add(int=1)
。它们真的不同吗?可能重复@Constructor是的,我忘了在第二个模板上添加
。修复了!
template<typename T,typename>
void Add(T);
template <typename T, typename>
void Add(T);