1

There are 2 SFINAE snippets that I coded.
They do exactly the same thing.
However, the first one works, while the second doesn't.
Why? (The second is more similar to my real program.)

This code works ( http://coliru.stacked-crooked.com/a/50e07af54708f076 )

#include <iostream>
#include  <type_traits>
enum EN{ EN1,EN2 };
template<EN T1> class B{
    public: template<EN enLocal=T1> typename     
      std::enable_if<enLocal==EN1,void>::type test(){  std::cout<<"1"<<std::endl;}  
    public: template<EN enLocal=T1> typename    
      std::enable_if<enLocal==EN2,void>::type test(){  std::cout<<"2"<<std::endl; }
};

int main(){
    B<EN1> b;
    b.test(); 
}

But this code is uncompilable (http://coliru.stacked-crooked.com/a/28b6afd443b36c7e) :-

#include <iostream>
#include  <type_traits>
enum EN{ EN1,EN2 };
class Base{
    public: void test(){
        std::cout<<"1"<<std::endl;
    };
};
template<EN T1> class B : public Base{
    public: template<EN enLocal=T1> 
      std::enable_if_t< enLocal==EN2,void > test(){
        std::cout<<"2"<<std::endl;
    }
};
int main(){
    B<EN1> bEn1; bEn1.test(); //should print 1
    //B<EN2> bEn2; bEn2.test(); //print 2
}

I am very new to SFINAE and still learning it via https://stackoverflow.com/a/50562202/.

cppBeginner
  • 1,114
  • 9
  • 27

2 Answers2

3

This code has two issues:

  1. When invoking bEn1.test(); or bEn2.test(); compiler will figure out that name test refers to the function in class B and the set of overloaded functions will include only B::test. This can be fixed by brining name from the base class into derived class:
template<EN T1> class B : public Base{
    public: using Base::test;
  1. However now non-template function will be preferred over template function (even when enable_if works) so B<EN2> bEn2; bEn2.test(); will print 1.

In order to make this work again you can introduce yet another overload similar to one in the first example that will invoke function from the base class instead of bringing Base::test name into derived class:

public: template<EN enLocal=T1> 
    std::enable_if_t< enLocal!=EN2,void > test(){
       return Base::test();
}

Another possible C++17-style workaround utilizing if constexpr instead of type traits or SFINAE:

public: template<EN enLocal = T1> void
test()
{
    if constexpr(EN2 == enLocal)
    {
        std::cout<<"2"<<std::endl;
    }
    else
    {
         Base::test();
    }
}
user7860670
  • 35,849
  • 4
  • 58
  • 84
1

Depending on what is the real use case, you could also consider some form of tag dispatching:

enum class En {
    base, a, b, c
};

template<En Type> void test_impl()
{
    if constexpr (Type == En::base)
        std::cout << "Base\n";
    else if constexpr (Type == En::a)
        std::cout << "1\n";
    else if constexpr (Type == En::b)
        std::cout << "2\n";
    else
        std::cout << "Default\n";
}

struct Base {
    void test() {
        std::cout << "Base - ";
        test_impl<En::base>();  
    }
};

template<En Type>
struct Derived : public Base {
    void test() {
        std::cout << "Derived - ";
        test_impl<Type>();
    }  
};

int main()
{
    Base b;
    b.test();              // -> "Base - Base"
    Derived<En::a> b1;
    b1.test();             // -> "Derived - 1" 
    Derived<En::b> b2;
    b2.test();             // -> "Derived - 2"
    Derived<En::base> b3;
    b3.test();             // -> "Derived - Base"
    Derived<En::c> b4;
    b4.test();             // -> "Derived - Default"
}
Bob__
  • 12,361
  • 3
  • 28
  • 42