为什么enable_if的行为不符合预期?

时间:2017-10-30 08:20:50

标签: c++ templates c++14 typetraits

有人可以帮我理解为什么以下代码

1)不会导致error: redefinition of 'foo'

2)为什么它输出T而不是A&

#include <type_traits>
#include <iostream>

class A{};

template< typename T >
void foo( T&& )
{
   std::cout << "T" << std::endl;
}

template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::type > >
void foo( A_t&& )
{
   std::cout << "A&" << std::endl;
}

template< typename A_t, std::enable_if_t< std::is_same<A_t, A>::type > >
void foo( A_t&& )
{
   std::cout << "A" << std::endl;
}


int main()
{
  A a;
  foo( a );
}

3 个答案:

答案 0 :(得分:2)

您错误地使用了std::enable_if_t。启用的定义也必须与主模板匹配。因此,您可以在返回类型上启用enable_if。不出所料,这个定义现在含糊不清。

#include <iostream>
#include <type_traits>

class A {};

template< typename T >
void foo( T&& )
{
    std::cout << "T" << std::endl;
}

template< typename A_t >
std::enable_if_t< std::is_same<A_t, A&>::value >
foo( A_t&& )
{
    std::cout << "A&" << std::endl;
}

template< typename A_t >
std::enable_if_t< std::is_same<A_t, A>::value >
foo( A_t&& )
{
    std::cout << "A" << std::endl;
}

int main()
{
    A a;
    foo( a );
}

为什么你不仅仅为fooA&&重载A

答案 1 :(得分:2)

这两个问题的答案是&#34;因为您没有正确使用enable_if_t&#34;。

模式是

template< typename A_t, 
          typename = std::enable_if_t< std::is_same<A_t, A&>::value > >

注意:

  1. typename =引入了一个未命名的类型模板参数。
  2. 您需要::value,而非::type才能传递给std::enable_if_t
  3. 使用正确的模式后,第二个和第三个定义会出现重新定义错误(因为单独的默认模板参数不能重载)。

    对于现在的代码,第二个模板参数的替换总是失败,因为std::enable_if_t想要一个非类型的布尔参数,而std::is_same<A_t, A&>::type是一个类型。由于这些模板没有有效的实例化,因此行为未定义。

答案 2 :(得分:1)

编译器更喜欢:

template< typename T >
void foo( T&& )
{
   std::cout << "T" << std::endl;
}

在:

template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::type>>
void foo( A_t&& )
{
   std::cout << "A&" << std::endl;
}

template< typename A_t, std::enable_if_t< std::is_same<A_t, A>::type>>
void foo( A_t&& )
{
   std::cout << "A" << std::endl;
}

因为后两个重载会引入non-deduced context

正如其他答案中所述,std::enable_if的应用不正确。要应用SFINAE,您必须通过以下方式之一更改代码:

选项1:

template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::value>* = nullptr>
void foo( A_t&& )
{
   std::cout << "A&" << std::endl;
}

template< typename A_t, std::enable_if_t< std::is_same<A_t, A>::value>* = nullptr>
void foo( A_t&& )
{
   std::cout << "A" << std::endl; 
}

选项2:

template< typename A_t>
std::enable_if_t< std::is_same<A_t, A&>::value>
foo( A_t&& )
{
   std::cout << "A&" << std::endl;
}

template< typename A_t>
std::enable_if_t< std::is_same<A_t, A>::value>
foo( A_t&& )
{
   std::cout << "A" << std::endl; 
}

现在,如果您在上述其中一个选项中更改了代码,则会出现歧义编译错误,导致重载解析无法在template<typename A_t, std::enable_if_t<std::is_same<A_t, A&>::value>* = nullptr> void foo(A_t&&)template<typename T> void foo(T&&)之间进行选择。< / p>

一旦你摆脱了模糊的调用(即摆脱template<typename T> void foo( T&& )),代码将编译并运行,并且由于转发参考折叠规则将选择template< typename A_t, std::enable_if_t< std::is_same<A_t, A&>::value>> void foo( A_t&& )

  • A和&安培;成为A&amp;
  • A和&安培;&安培;成为A&amp;
  • A&安培;&安培; &安培;成为A&amp;
  • A&安培;&安培; &安培;&安培;成为A&amp;&amp;

在您的情况下,alvalue,因此A_t将被推断为A&,并且由于SFINAE,首选超负荷是首选。