SFINAE with concepts

106 Views Asked by At

I have an abstract base class and then a templated derived class, for certain types (specified by a template), I want to implement the virtual base methods in the derived class, for others, this should be left open (with the need for a further derivation):

#include <iostream>

struct cat {
void meow() {
        std::cout << "Meow!\n";
    }
};

struct base {
    virtual void foo_base() = 0;
};

template<typename T>
concept can_meow = requires(T t){t.meow();};

template<class T>
struct derived: public base {
derived() requires can_meow<T> {
    std::cout << "Wrapper constructor with meow!\n";
}

derived() requires (!can_meow<T>) {
    std::cout << "Wrapper constructor w/o meow!\n";
}

void foo_base() requires can_meow<T> final {};
};

template<class T>
struct derived2: public derived<T> {
    void foo_base() final {};
};

int main() {
    derived<cat> c; //works
    //derived2<int> i; //error
    return 0;
}

How do I get this to work? The error I'm currently getting is:

<source>:28:6: error: use of function 'void wrapper<T>::foo_base() requires  can_meow<T> [with T = int]' with unsatisfied constraints

Cheers and thanks

Update: godbolt https://godbolt.org/z/T4v7Yjq5f

2

There are 2 best solutions below

1
Artyer On

You are not allowed to constrain a virtual function in any way (See: [class.virtual]p6).

You can achieve a similar effect by having a conditional base class that could override the function. For example:

namespace detail {
    template<class T>
    struct derived_impl : base {
        using base::base;
    };

    template<can_meow T>
    struct derived_impl<T> : base {
        using base::base;

        void foo_base() final {}
    };
}

template<class T>
struct derived: detail::derived_impl<T> {
    derived() requires can_meow<T> {
        std::cout << "Wrapper constructor with meow!\n";
    }

    derived() requires (!can_meow<T>) {
        std::cout << "Wrapper constructor w/o meow!\n";
    }
};
0
Yakk - Adam Nevraumont On

As @artyer already noted, you cannot constrain virtual functions this way. The technique of introducing a conditional intermediate class is a good one, but you can do some CRTP trickery to make it less intrusive.

template<class D, class B, bool route_foo>
struct foo_router:B {
  using B::B;
};
template<class D, class B>
struct foo_router<D,B, true>:B {
  using B::B;
  void foo_base() final {
    return static_cast<D*>(this)->foo_base_impl();
  }
};

then we do:

template<class T>
struct derived: public foo_router<derived<T>, base, can_meow<T>> {
  derived() requires can_meow<T> {
    std::cout << "Wrapper constructor with meow!\n";
  }

  derived() requires (!can_meow<T>) {
    std::cout << "Wrapper constructor w/o meow!\n";
  }

  void foo_base_impl() requires can_meow<T> /*final*/ {};
};

template<class T>
struct derived2: public derived<T> {
  void foo_base() final {};
};

the foo_router conditionally routes foo_base to foo_base_impl depending on can_meow<T>. In this implementation, it exists regardless, but does nothing if !can_meow<T>.

Live example - the changes are isolated to renaming and stripping final from foo_base_impl, the extra router class, and inheritance from derived.

We can make this more light weight:

template<class D, class B, bool route_foo>
using foo_router_t = std::conditional_t<route_foo, foo_router<D,B,route_foo>, B>;

this eliminates the empty foo_router type at compile time. Having fewer types can save on compile time and memory use.

template<class T>
struct derived: public foo_router_t<derived<T>, base, can_meow<T>>