std::enable_if : parameter vs template parameter [duplicate]
Asked Answered
C

3

38

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.

Cadal answered 15/6, 2012 at 18:1 Comment(3)
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++Ramification
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 ;-)Motivity
@AJG85: inline on a template affects explicit instantiations, which might be a good or a bad thing.Signorina
M
45

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();
   }
};
Mamie answered 15/6, 2012 at 18:32 Comment(6)
Pretty clever solution!Squire
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?Crosseyed
@Crosseyed 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 =)Mamie
@JohannesSchaub-litb, it is non-type template parameter with default value 0, which is part of signature. no?Kane
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.Blackjack
I always wondered with the default template parameter was void and it wasn't something assignable, like int, this way we could write simply std::enable_if_t<Condition> =0. Even void* would have been better in this sense.Tymbal
C
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.

Collado answered 15/6, 2012 at 18:18 Comment(1)
This only works if the function has a return type. Counterexample: constructors.Standoff
I
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.

Interdependent answered 2/8, 2018 at 18:12 Comment(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.Lamberto

© 2022 - 2024 — McMap. All rights reserved.