带有is_enum的enable_if不起作用

时间:2015-04-21 04:17:22

标签: c++ c++11 sfinae

MCVE:

#include <type_traits>

template<typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
{
}

enum class Bar { a,b,c };

int main()
{
    Bar bar{Bar::a};
    func(bar, 1);
}

我希望func(bar, 1);符合我对func的定义,但g ++报告:

sfi.cc: In function 'int main()':
sfi.cc:13:17: error: no matching function for call to 'func(Bar&, int)'
      func(bar, 1);
                 ^
sfi.cc:13:17: note: candidate is:
sfi.cc:4:10: note: template<class T> bool func(typename std::enable_if<std::is_e
num<_Tp>::value, T>::type&, int)
     bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
          ^
sfi.cc:4:10: note:   template argument deduction/substitution failed:
sfi.cc:13:17: note:   couldn't deduce template parameter 'T'
      func(bar, 1);
                 ^

为什么这不起作用,如何解决?

背景:这是this problem

的尝试解决方案

3 个答案:

答案 0 :(得分:9)

template<typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
上面在非推断的上下文中使用了

T。这意味着它不会扣除T,因为它(在一般情况下)需要逆转任意的图灵完全转换,这是不可能的。

func的含义是第一个参数是enum class Bar,第二个参数是int。从此,您希望它推导出T

虽然将T设置为enum class Bar确实可以解决问题,但C ++并不能猜测。它模式匹配。

假设我们有:

template<class T>
struct blah { using type=int; };
template<>
struct blah<int> { using type=double; };

然后

template<class T>
bool func( typename blah<T>::type );

如果有人将int传递给func,应该为T推断出哪种类型?这是一个玩具示例:foo<T>::type可以执行图灵完备算法,将T映射到相关类型。在一般情况下,不可能反转或甚至确定逆是否是模糊的。因此,即使在简单的情况下,C ++也不会尝试,因为简单和复杂之间的边缘很快就会模糊。

解决问题:

template<class T,class=typename std::enable_if< std::is_enum<T>::value >::type>
bool func( T &t, int x ) {
}

现在T用于推断的上下文中。 SFINAE仍然存在,但不会阻止模板类型扣除。

或者你可以等待C ++ 1z概念,它可以自动化上述结构(基本上)。


查看链接的问题,解决问题的简便方法是使用标记调度。

template<typename T> 
bool func(T &t, int x)
{
    // do stuff...
}

但是我希望有三个不同的功能体:

我们有3个案例:

  • T是一个枚举

  • 是无符号字符

  • 其他所有

所以,派遣:

namespace details {
  template<class T>
  bool func( T& t, int x, std::true_type /* is_enum */, std::false_type ) {
  }
  template<class T>
  bool func( T& t, int x, std::false_type, std::true_type /* unsigned char */ ) {
  }
  template<class T>
  bool func( T& t, int x, std::false_type, std::false_type ) {
    // neither
  }
}
template<class T>
bool func( T& t, int x ) {
  return details::func( t, x, std::is_enum<T>{}, std::is_same<unsigned char, T>{} );
}

现在正常的过载规则用于在3个函数之间进行选择。如果你以某种方式拥有enumunsigned char(不可能)的类型,则会出现编译时错误。

答案 1 :(得分:5)

您在非推断的上下文中使用模板参数T

来自§14.8.2.5/ 5 [temp.deduct.type]

  

未推断的背景是:
   - 使用 qualified-id 指定的类型的嵌套名称说明符
   - ......

要解决此问题,请将enable_if移至虚拟模板参数

template<typename T,
         typename = typename std::enable_if< std::is_enum<T>::value, T >::type>
bool func( T &t, int x )
{
  // ...
}

Live demo


查看您链接的问题,您尝试根据第一个参数是func来切换enum的两个定义。在这种情况下,上述解决方案将无法工作,因为默认模板参数不是函数模板签名的一部分,并且最终会出现多个定义错误。

有两种不同的方法可以解决这个问题,使用虚拟模板参数

template<typename T,
         typename std::enable_if< std::is_enum<T>::value, int >::type* = nullptr>
bool func( T &t, int x )
{
  // ...
}

或在返回类型

中使用enable_if表达式
template<typename T>
typename std::enable_if< std::is_enum<T>::value, bool >::type 
    func( T &t, int x )
{
    // ...
}

答案 2 :(得分:2)

您看到的错误与自动类型扣除有关,而不是enable_ifis_enum

以下作品。

#include <iostream>
#include <type_traits>

template<typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
{
   return true;
}

enum class Bar { a,b,c };

int main()
{
    Bar bar{Bar::a};
    std::cout << func<decltype(bar)>(bar, 1) << std::endl;
}

<强>更新

正如OP已经发现的那样,使用包装函数可以避免使用decltype

#include <iostream>
#include <type_traits>

template <typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
{
   return true;
}

template <typename T>
bool func2( T &t, int x )
{
   return func<T>(t,x);
}

enum class Bar { a,b,c };

int main()
{
    Bar bar{Bar::a};
    std::cout << func2(bar, 1) << std::endl;
}