3

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

namezero
  • 2,203
  • 3
  • 24
  • 37

2 Answers2

2

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Ahh I read that and wasn't sure what that was supposed to do, but now I get it, as the type void = 0 will fail! Now it makes sense, thank you!! – namezero Apr 21 '20 at 17:58
  • 1
    @namezero no, its not `void = 0`, `std::enable_if` has a `::type = int` and `std::enable_if` does not have have it. Don't worry, even after many attempts i am still puzzled by SFINAE. I can understand code when I see it, but have no clue how to get there. I was just lucky to find that note ;) – 463035818_is_not_an_ai Apr 21 '20 at 18:28
  • Yes, so one would be an unnamed type int with default = 0 and the other one fails and that's why it discarded I suppose. Shame on me for discarding the note as "don't understand" :D – namezero Apr 22 '20 at 05:47
  • 1
    @namezero no its not an unnamed type. Its like if you had a template `template struct foo {}` and in a second template you use `foo::type`. It is a substitution failure – 463035818_is_not_an_ai Apr 22 '20 at 07:06
1

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";
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Thank you for the 17 version. This is very useful in the future; the library in use though does not support it yet. The C++11 version is almost what I had, save for the typename! – namezero Apr 21 '20 at 18:02
  • @namezero You are welcome! Yes, they are very similar. I moved the sfinae part to the return type to kill two birds with one stone so to speak :) – Ted Lyngmo Apr 21 '20 at 18:04