Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enable_if in function members for void and inheritance

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.

like image 452
svoltron Avatar asked Dec 17 '22 19:12

svoltron


2 Answers

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.

like image 169
StoryTeller - Unslander Monica Avatar answered Dec 28 '22 22:12

StoryTeller - Unslander Monica


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

like image 27
YSC Avatar answered Dec 29 '22 00:12

YSC