4

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.

svoltron
  • 365
  • 1
  • 10
  • 1
    Possible duplicate of [Selecting a member function using different enable\_if conditions](https://stackoverflow.com/questions/13401716/selecting-a-member-function-using-different-enable-if-conditions) – Daniel Langr Jan 15 '19 at 09:24
  • question edited @DanielLangr – svoltron Jan 15 '19 at 09:43
  • 1
    Please don't edit a question in a way that makes existing answers incorrect or incomplete. For extra details like this, open a second followup question, which can link to the first. (But leave this one as is, now that there are answers addressing both versions.) – aschepler Jan 15 '19 at 12:48
  • Ok, makes totally sense. Thank you for the suggestion – svoltron Jan 15 '19 at 13:25
  • Hard to choose the best answer, thank you to all, and sorry for the edit – svoltron Jan 16 '19 at 07:46

4 Answers4

5

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

Oliv
  • 17,610
  • 1
  • 29
  • 72
YSC
  • 38,212
  • 9
  • 96
  • 149
  • Thank you for the feedback. Is this the only way to make it work? Suppose that the Test class inherits from TestBase and that execute() is virtual, would there be problems with that? – svoltron Jan 15 '19 at 09:17
  • 1
    @svoltron Yes. The virtual function already exists and sfinae kicks in when a template is being instanciated. What you could do is to provide sfinae function templates calling the virtual function. But this is out of the scope of this question. You can ask another one. – YSC Jan 15 '19 at 09:19
  • Mmm I'll try to think about it and if I won't get it I'll ask another question then, thank you – svoltron Jan 15 '19 at 09:29
  • 1
    @svoltron You're welcome. Note: it's expected that you accept an answer by clicking the checkmark below the answer's score ;) – YSC Jan 15 '19 at 09:49
  • 1
    @Oliv True - but the virtual functions were added to the question after this answer. – aschepler Jan 15 '19 at 12:46
  • The question has been edited so that `execute` are now virtual functions. This solution does not work any more because a template function member cannot be an overrider. – Oliv Jan 15 '19 at 12:56
  • my mistake, sorry about that – svoltron Jan 15 '19 at 13:34
  • Yep, thank you @Oliv. That's a downvote on the question from me. OP should have ask another question in my opinion. That kind of edit invalidate the work done for the first version. – YSC Jan 15 '19 at 14:04
  • Thank you, this was helpful. I needed this in conjunction with a fowarding (virtual) function for my purposes. – Joe Marley Jan 16 '23 at 18:13
4

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thank you for your feedback. Interesting solution indeed. The only problem with this is that if there are other function members besides execute() I have to write them again for the specialization. However maybe this approach works for the inheritance problem I had in mind: basically making Test::execute override a TestBase::execute virtual function. In the other way it seems that the virtual mechanism doesn't work – svoltron Jan 15 '19 at 09:29
  • 1
    @svoltron - I don't know about your entire problem, but I don't see why you need to add virtual functions to the mix. `TestBase` can also hold data members to accomplish the task `execute` is meant to do. If you make it virtaul, you get back to your original problem also. You need to figure out which `execute` is in `TestBase` in order to override it! – StoryTeller - Unslander Monica Jan 15 '19 at 09:31
  • As I wrote in the comment to @Angew reply I will inject the base class in some way, there's a sense, trust me or assume there is. Directly inheriting from this kind of base class and using the Angew/YSC approach the virtual call does not get executed. Instead the Base class function call is. – svoltron Jan 15 '19 at 09:40
  • @svoltron - Um. I'm not sure I quite get your approach from the comment. I think you should follows YSC's advice. If you ask another question (hopefully one on-topic for [so]), you'll be able to explain more. – StoryTeller - Unslander Monica Jan 15 '19 at 09:42
  • sure, thank you anyway. I have edited the question, but I'll ask another one if that's possible – svoltron Jan 15 '19 at 09:44
  • Sure it helps, haven't yet tried but I trust you ehhe. Really interesting one. I was hoping it was possible with enable_if but it doesn't seem so. Thank you very much – svoltron Jan 15 '19 at 10:05
2

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 */
        }
      }
  };
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • I'm using C++11 and probably the CRTP one is more elegant as you say. With C++17 it's much better indeed – svoltron Jan 15 '19 at 13:33
1

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
};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Thank you again for the feedback. This seems similar to the @StoryTeller one, but includes also the CRTP injection part. So this seems to be the only way – svoltron Jan 15 '19 at 10:07
  • 1
    @svoltron I hope it helps. But please try to avoid [XY problems](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) in future questions, they're very frustrating to answer. – Angew is no longer proud of SO Jan 15 '19 at 10:11