Mark Lakata
Mark Lakata

Reputation: 20898

Can't override virtual function using SFINAE (std::enable_if)

The code below overrides virtual A::foo() with B::foo() override. If the BROKEN is not defined, then it compiles just fine. My understanding of the std::enable<std::is_pod<Q>::value>::type shenanigans is that it effectively replaces the SFINAE expression with void, and this should be identical to plain old void.

However, it won't compile. I get compiler errors:

Intel compiler (icpc):

$ make a CXXFLAGS="-std=c++11 -DBROKEN"
icpc -std=c++11 -DBROKEN    a.cpp   -o a
a.cpp(24): error: object of abstract class type "B<int>" is not allowed:
            pure virtual function "A::foo" has no overrider
     B<int> b;
            ^

gcc:

$ make a CXXFLAGS="-std=c++11 -DBROKEN" CXX=g++
g++ -std=c++11 -DBROKEN    a.cpp   -o a
a.cpp: In function ‘int main()’:
a.cpp:24:11: error: cannot declare variable ‘b’ to be of abstract type ‘B<int>’
    B<int> b;
           ^
a.cpp:9:7: note:   because the following virtual functions are pure within ‘B<int>’:
 class B : A
       ^
a.cpp:5:16: note:       virtual void A::foo()
   virtual void foo() = 0;
                ^
make: *** [a] Error 1

Here is the test case:

#include <type_traits>

class A {
  public:
  virtual void foo() = 0;
};

template<typename T>
class B : A
{
  public:

#ifdef BROKEN
  template<class Q = T>
  typename std::enable_if<true>::type
#else
  void
#endif
  foo() override { }
};

int main()
{
   B<int> b;
   b.foo();
   return 0;
}

If I don't use virtual functions, the std::enable<true> thing works as expected.

addendum

Those results were for gcc 4.8.3. With gcc 5.3, I get one extra line of compiler error:

a.cpp:19:9: error: member template ‘std::enable_if<true>::type B<T>::foo()’ may not have virt-specifiers
   foo() override { }

     ^

Why not?

Upvotes: 4

Views: 2310

Answers (1)

Mark Lakata
Mark Lakata

Reputation: 20898

This is not pretty, but it got the job done... virtual override with a SFINAE conditional compilation. It has more call overhead (foo() calls foo_x()), but maybe the compiler will optimize that out.

#include <type_traits>

class A {
  public:
  virtual void foo() = 0;
};

template<typename T>
class B : A
{
  public:

  template<class Q = T>
  typename std::enable_if<true>::type
  foo_x() { }

  void foo() override { foo_x(); }
};

int main()
{
   B<int> b;
   b.foo(); // not an example of polymorphism! just want it to compile.
   return 0;
}

That example is merely to prove that it compiles and is useless. The actual implementations of foo_x will have multiple implementations:

  template<class Q = T>
  typename std::enable_if<is_pod<Q>::value>::type
  foo_x() { /* do something with POD */}

  template<class Q = T>
  typename std::enable_if<std::is_base_of<std::string,Q>::value>::type
  foo_x() { /* do something with a std::string */}

  template<class Q = T>
  typename std::enable_if<std::is_base_of<MyCrazyClass,Q>::value>::type
  foo_x() { /* do something with a derived class of MyCrazyClass */}

Upvotes: 7

Related Questions