enable_if in function members for void and inheritance

Viewed 686

I'm trying to understand why this code does not compile:

// test.h
struct Base
  {
  virtual ~Base{};
  virtual void execute() {}
  virtual void execute(int) {}
  virtual void execute(double) {}
  }

template<class T>
struct Test : Base
  {
    void execute(typename std::enable_if<std::is_void<T>::value, void>::type)
      {
      // Do A
      }

    void execute(typename std::enable_if<!std::is_void<T>::value, int>::type t)
      {
      // Do B
      }
  };

// main.cpp
Test<void> t; 

I get a compiler error: "no type named type".

Same error even if I modify the A version of the code with

std::enable_if<std::is_void<T>::value>

The goal is to create a class that depending on the parameter T creates a different function members. In this case 2, but I'd be interested also in more.

[Edit] I've added the inheritance part I was talking about in the comments.

4 Answers

Note: this answer is valuable for a previous edit of the question. The recent edit has drastically changed the question and this answer is not adequate anymore.

Because execute is not a template function, there could be no SFINAE involevd. Indeed, whenever Test<void> is instantiated, both versions of execute are, which leads to an error that is not a template deduction failure.

You need a function template (let call the template parameter U) in order to benefit from SFINAE; and since you need to use the same type template argument of Test (T), you can provide a default argument U = T):

Solution:

template<class T>
struct Test
{
    template<class U = T>
    std::enable_if_t<std::is_void_v<U>> execute()
    { std::cout << "is_void\n"; }

    template<class U = T>
    std::enable_if_t<!std::is_void_v<U>> execute()
    { std::cout << "!is_void\n"; }
};

Live demo

When you instantiated Test<void>, you also instantiated the declarations of all of it's member functions. That's just basic instantiation. What declarations does that give you? Something like this:

void execute(void);
void execute(<ill-formed> t);

If you were expecting SFINAE to silently remove the ill-formed overload, you need to remember that the S stands for "substitution". The substitution of template arguments into the parameters of a (member) function template. Neither execute is a member function template. They are both regular member functions of a template specialization.

You can fix it in a couple of ways. One way would be to make those two templates, do SFINAE properly, and let overload resolution take you from there. @YSC already shows you how.

Another way is to use a helper template. This way you get your original goal, for a single member function to exist at any one time.

template<typename T>
struct TestBase {
  void execute(T t) { }
};

template<>
struct TestBase<void> {
  void execute() { }
};

template<class T>
struct Test : private TestBase<T> {
  using TestBase<T>::execute;
};

You can choose whichever works best for your needs.


To address your edit. I think the second approach actually fits your needs better.

template<typename T>
struct TestBase : Base {
  void execute(T t) override { }
};

template<>
struct TestBase<void> : Base {
  void execute() override { }
};

TestBase is the middle man that accomplishes what you seem to be after.

There is also an other option, which is less elegant than the one using CRTP. It consists in choosing in the body of the overrider whither it forward to the base implementation or provides a new implementation of the function.

If you were using c++17 it could be straightforward thanks to if constexpr. In c++11, the alternative is to use tag dispatch:

template<class T>
struct Test : Base
  {
    void execute()
      {
      void do_execute(std::integral_constant<bool,std::is_void<T>::value>{});
      }

    void execute(int t)
      {
      void do_execute(std::integral_constant<bool,!std::is_void<T>::value>{}, t);
      }
  private:
  void do_execute(std::integral_constant<bool,true>){
       /*implementation*/
       }
  void do_execute(std::integral_constant<bool,false>){
       Base::execute();//Call directly the base function execute.
                       //Such call does not involve the devirtualization
                       //process.
       }
  void do_execute(std::integral_constant<bool,true>,int t){
       /*implementation*/
       }
  void do_execute(std::integral_constant<bool,false>,int t){
       Base::execute(t);//Call directly the base function execute.
                        //Such call does not involve the devirtualization
                        //process.
       }
  };

With C++17 if constexpr it could look more elegant than the CRTP solution:

template<class T>
struct Test : Base
  {
    void execute(){
      if constexpr (is_void_v<T>){
         Base::execute();
         }
      else{
        /* implementation */
        }
      }

    void execute(int t){
      if constexpr (!is_void_v<T>){
         Base::execute(t);
         }
      else{
        /* implementation */
        }
      }
  };

You can encapsulate the different overloads of execute in a set of related helper classes, something like this:

template <class T>
struct TestHelper : Base
{
  void execute(int) override {}
};

template <>
struct TestHelper<void> : Base
{
  void execute() override {}
};


template <class T>
struct Test : TestHelper<T>
{
  // Other Test stuff here
};

If the implementations of execute actually depend on "Other Test stuff" which should be shared between then, you can also employ CRTP:

template <class T, class Self>
struct TestHelper : Base
{
  void execute(int) override
  {
    Self& self = static_cast<Self&>(*this);
    // Now access all members through `self.` instead of directly
  }
};

template <class Self>
struct TestHelper<void, self> : Base
{
  void execute() override
  {
    Self& self = static_cast<Self&>(*this);
    // Now access all members through `self.` instead of directly
  }
};

template <class T>
struct Test : TestHelper<T, Test>
{
  // Other Test stuff here
};
Related