37

I'm building some input checker that needs to have specific functions for integer and/or double (for example 'isPrime' should only be available for integers).

If I'm using enable_if as a parameter it's working perfectly :

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();
   }   
};

but if I'm using it as a template paramater (as demonstrated on 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();
   }
};

then I have the following error :

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()’

I can't figure out what is wrong in the second version.

Loïc Février
  • 7,540
  • 8
  • 39
  • 51
  • Possibly irrelevant but in VS2010 I can't do that because default template arguments are only allowed for class templates - I don't know about g++ – David Jun 15 '12 at 18:09
  • 3
    This is pedantic but the `inline` keyword on a member method or template isn't needed and certainly not a member that is also a template ;-) – AJG85 Jun 15 '12 at 18:20
  • @AJG85: `inline` on a template affects explicit instantiations, which might be a good or a bad thing. – Davis Herring Jan 15 '20 at 04:42

3 Answers3

44

Default template arguments are not part of the signature of a template (so both definitions try to define the same template twice). Their parameter types are part of the signature, however. So you can do

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();
   }
};
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Pretty clever solution! – plasmacel Sep 17 '15 at 20:51
  • 1
    So a default type template parameter (which has a default type) is not part of the signature of a template, but a default non-type template parameter (which has a default constant integral value) is. Is that correct? – Alan Jun 24 '16 at 19:42
  • 4
    @Alan that is incorrect. both aren't part of the signature. The `= 0` I have written only serves the purpose of making the user not pass the `0` himself (alternatively, you may also write `...` instead of `= 0`, which will also not require arguments, but default to an empty pack. I consider that obscure thou and I like the int better). It's not needed intrinsically for the SFINAE to work. It's also `= 0` in both cases, so if it would be part of the signature, it wouldn't help to distinguish in my example anyway =) – Johannes Schaub - litb Jun 25 '16 at 07:26
  • 1
    @JohannesSchaub-litb, it is non-type template parameter with default value 0, which is part of signature. no? – pepero Sep 10 '17 at 21:37
  • Different non-type values makes different types, so compiler will continue to analyze `enable_if` parts. SFINAE works in `enable_if` blocks. `=0` works when winner picked. – user3059627 Jul 04 '22 at 03:14
10

The problem is that the compiler sees 2 overloads of the same method, both which contain the same arguments(none, in this case) and the same return value. You can't provide such definition. The cleanest way to do this is to use SFINAE on the function's return value:

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();
   }
};

That way, you're providing 2 different overloads. One returns an int, the other one returns a double, and only one can be instantiated using a certain T.

mfontanini
  • 21,410
  • 4
  • 65
  • 73
7

I know this question is about std::enable_if, however, I like to provide an alternative solution to solve the same problem without enable_if. It does require 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();
   }   
};

This code looks more as if you would write it at runtime. All branches have to be syntactic correct, however the semantics don't have to be. In this case, if T is int, the getDouble ain't causing compilation errors (or warnings) as it doesn't get checked/used by the compiler.

If the return type of the function would be to complex to mention, you can always use auto as the return type.

JVApen
  • 11,008
  • 5
  • 31
  • 67
  • 1
    there is a time and a place for both, and your answer to this question reminded me this solution is the better one for my use case. so thanks. – Conrad Jones Mar 03 '19 at 13:04