2
votes

I have read the following threads:

no type named ‘type’ in ‘struct std::enable_if<false, void>

Selecting a member function using different enable_if conditions

"What happened to my SFINAE" redux: conditional template class members?

However, I seem to be unable to make this fairly simple SFINAE problem work on either gcc and msvc:

#include <type_traits>
#include <iostream>

template<typename A, typename B>
class Test {
public:

  template<typename X=A, typename = typename std::enable_if<std::is_same<X, void>::value, void>::type >
  void foo() {
    std::cout << "A";
  }

  template<typename X=A, typename = typename std::enable_if<!std::is_same<X, void>::value, void>::type >
  void foo() {
    std::cout << "B";
  }


};

int main(int argc, char **argv) {

  Test<int, float> t;

  t.foo();

  return 0;
}

Actual result:

A = void: Full error:

main.cpp:15:8: error: 'template<class A, class B> template<class X, class> void Test<A, B>::foo()' cannot be overloaded with 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   15 |   void foo() {
      |        ^~~
main.cpp:10:8: note: previous declaration 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   10 |   void foo() {
      |        ^~~

A = int: Full error:

main.cpp:15:8: error: 'template<class A, class B> template<class X, class> void Test<A, B>::foo()' cannot be overloaded with 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   15 |   void foo() {
      |        ^~~

main.cpp:10:8: note: previous declaration 'template<class A, class B> template<class X, class> void Test<A, B>::foo()'
   10 |   void foo() {
      |        ^~~

main.cpp: In function 'int main(int, char**)':

main.cpp:26:9: error: no matching function for call to 'Test<int, float>::foo()'
   26 |   t.foo();
      |         ^

main.cpp:10:8: note: candidate: 'template<class X, class> void Test<A, B>::foo() [with X = X; <template-parameter-2-2> = <template-parameter-1-2>; A = int; B = float]'
   10 |   void foo() {
      |        ^~~

main.cpp:10:8: note:   template argument deduction/substitution failed:

main.cpp:9:26: error: no type named 'type' in 'struct std::enable_if<false, void>'
    9 |   template<typename X=A, typename = typename std::enable_if<std::is_same<X, void>::value, void>::type >
      |                          ^~~~~~~~

Expected result

A = void: Outputs "A"

A = int: Outputs "B"

What I want is to implement a different (additional) member function based on a template parameter. However, it seems like I cannot make the enable_if dependent on the class template types, but I am not sure why. According to the linked threads, the code above appears correct. Could you please explain why this is not working?

Live Link

2
you skipped the important part of the compiler error. Better incldue the complete error in the question463035818_is_not_a_number
I have added the (I think) initial error, because the output would be very clobbered.namezero
but the error you did include in the question is just a consequence of the actual error (that you did not include)463035818_is_not_a_number
Ok, full compile output added. Apologies, I thought including the link would be betternamezero
no worries, adding a link to a online compiler is good, but not better ;)463035818_is_not_a_number

2 Answers

1
votes

The notes from cppreference show similar and explains why it does not work:

A common mistake is to declare two function templates that differ only in their default template arguments. This does not work because the declarations are treated as redeclarations of the same function template (default template arguments are not accounted for in function template equivalence).

/* WRONG */

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              typename = std::enable_if_t<std::is_integral<Integer>::value>
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              typename = std::enable_if_t<std::is_floating_point<Floating>::value>
    >
    T(Floating) : m_type(float_t) {} // error: treated as redefinition
};

/* RIGHT */

struct T {
    enum { int_t,float_t } m_type;
    template <typename Integer,
              std::enable_if_t<std::is_integral<Integer>::value, int> = 0
    >
    T(Integer) : m_type(int_t) {}

    template <typename Floating,
              std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
    >
    T(Floating) : m_type(float_t) {} // OK
};

Applying the same fix to your code, makes it output the desired B :

#include <type_traits>
#include <iostream>

template<typename A, typename B>
class Test {
public:

  template<typename X = A,std::enable_if_t<std::is_same<X, void>::value, int> = 0>
  void foo() {
    std::cout << "A";
  }

  template<typename X=A,std::enable_if_t<!std::is_same<X, void>::value, int> = 0>
  void foo() {
    std::cout << "B";
  }


};

In your code the two function templates differed only in their default arguments. After the fix the second parameter is either int = 0 or a substition failure.

1
votes

Here's a C++17 version:

template<typename X=A>
std::enable_if_t<std::is_same_v<X, void>> foo() {
    std::cout << "A";
}

template<typename X=A>
std::enable_if_t<!std::is_same_v<X, void>> foo() {
    std::cout << "B";
}

(The default type for enable_if is void which is used as the type for the function)

You could also make it with constexpr if:

void foo() {
    if constexpr (std::is_same_v<A, void>) {
        std::cout << "A";      
    } else {
        std::cout << "B";
    }
}

C++11:

template<typename X=A>
typename std::enable_if<std::is_same<X, void>::value>::type foo() {
    std::cout << "A";
}

template<typename X=A>
typename std::enable_if<!std::is_same<X, void>::value>::type foo() {
    std::cout << "B";
}