std::enable_if: 参数 vs 模板参数

38
我正在构建一些输入检查器,需要具有整数和/或双精度的特定功能(例如,“isPrime”只能用于整数)。
如果我使用enable_if作为参数,它完美地工作:
template <class T>
class check
{
public:
   template< class U = T>
   inline static U readVal(typename std::enable_if<std::is_same<U, int>::value >::type* = 0)
   {
      return BuffCheck.getInt();
   }

   template< class U = T>
   inline static U readVal(typename std::enable_if<std::is_same<U, double>::value >::type* = 0)
   {
      return BuffCheck.getDouble();
   }   
};

但是如果我将它用作模板参数(如http://en.cppreference.com/w/cpp/types/enable_if所示)
template <class T>
class check
{
public:
   template< class U = T, class = typename std::enable_if<std::is_same<U, int>::value>::type >
   inline static U readVal()
   {
      return BuffCheck.getInt();
   }

   template< class U = T, class = typename std::enable_if<std::is_same<U, double>::value>::type >
   inline static U readVal()
   {
      return BuffCheck.getDouble();
   }
};

然后我遇到了以下错误:
error: ‘template<class T> template<class U, class> static U check::readVal()’ cannot be overloaded
error: with ‘template<class T> template<class U, class> static U check::readVal()’

我弄不清楚第二个版本有什么问题。

可能与此无关,但在VS2010中我无法这样做,因为默认模板参数仅允许用于类模板 - 我不知道g ++。 - David
3
这虽然有点学究气,但对于成员方法或模板上的 inline 关键字并不需要,特别是对于同时也是模板的成员更是如此 ;-) - AJG85
@AJG85:在模板上使用 inline 会影响显式实例化,这可能是好事也可能是坏事。 - Davis Herring
3个回答

45

默认模板参数不是模板签名的一部分(因此两个定义尝试两次定义相同的模板)。然而,它们的参数类型是签名的一部分。所以你可以这样做:

template <class T>
class check
{
public:
   template< class U = T, 
             typename std::enable_if<std::is_same<U, int>::value, int>::type = 0>
   inline static U readVal()
   {
      return BuffCheck.getInt();
   }

   template< class U = T, 
             typename std::enable_if<std::is_same<U, double>::value, int>::type = 0>
   inline static U readVal()
   {
      return BuffCheck.getDouble();
   }
};

相当聪明的解决方案! - plasmacel
1
那么,一个默认类型模板参数(具有默认类型)不是模板签名的一部分,但是一个默认非类型模板参数(具有默认常量整数值)是。这正确吗? - Alan
4
@Alan,那是不正确的。两者都不是签名的一部分。我写的= 0只是为了让用户不必自己传递0(或者你也可以用...代替= 0,它也不需要参数,但默认为空包。我认为后者比较晦涩,我更喜欢使用int)。它在SFINAE运作时本质上不是必需的。在这两种情况下,它也是= 0,所以如果它是签名的一部分,它在我的例子中也无法起到区分的作用 =) - Johannes Schaub - litb
1
@JohannesSchaub-litb,它是具有默认值0的非类型模板参数的一部分签名,对吗? - pepero
不同的非类型值会产生不同的类型,因此编译器将继续分析 enable_if 部分。SFINAE 在 enable_if 块中起作用。当选出赢家时,=0 才会起作用。 - user3059627
我一直很好奇为什么默认的模板参数是void,而不是可赋值的类型,比如int,这样我们就可以简单地写成std::enable_if_t<Condition> =0。即使是void*在这个意义上也更好一些。 - undefined

10

问题在于编译器看到了两个相同方法的重载,它们都包含相同的参数(在这种情况下是空)和相同的返回值。你无法提供这样的定义。最简单的方法是在函数的返回值上使用SFINAE:

template <class T>
class check
{
public:
   template< class U = T>
   static typename std::enable_if<std::is_same<U, int>::value, U>::type readVal()
   {
      return BuffCheck.getInt();
   }

   template< class U = T>
   static typename std::enable_if<std::is_same<U, double>::value, U>::type readVal()
   {
      return BuffCheck.getDouble();
   }
};

这样,您提供了两种不同的重载。一个返回int,另一个返回double,并且只有一个可以使用特定的T进行实例化。


只有当函数具有返回类型时,此方法才有效。反例:构造函数。 - L. F.

7

我知道这个问题涉及到 std::enable_if,但是我想提供一个不需要 enable_if 的替代解决方案来解决同样的问题。这需要 C++17 支持。

template <class T>
class check
{
public:
   inline static T readVal()
   {
        if constexpr (std::is_same_v<T, int>)
             return BuffCheck.getInt();
        else if constexpr (std::is_same_v<T, double>)
             return BuffCheck.getDouble();
   }   
};

这段代码看起来更像是你在运行时编写的。所有分支必须在语法上正确,但语义不必如此。 在这种情况下,如果 T 是 int,getDouble 不会引起编译错误(或警告),因为编译器没有检查/使用它。

如果函数的返回类型太复杂无法说明,您总可以使用 auto 作为返回类型。


1
有时候,不同的情况需要不同的解决方案。你对这个问题的回答让我想起了这个更适合我的用例的解决方案。所以谢谢你。 - Conrad Jones

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接