Vince
Vince

Reputation: 4439

c++ type traits : ensuring a subclass implements a method

There is a virtual class C.

I would like to ensure that any concrete subclass inheriting from C implements a function "get" (and have a clear compile time error if one does not)

Adding a virtual "get" function to C would not work in this case, as C subclasses could implement get functions of various signatures.

(in the particular case I am working on, pybind11 will be used to creates bindings of the subclasses, and pybind11 is robust of the "get" method of B to have a wide range of signatures)

Checking at compile time if a class has a function can be done with type traits, e.g.

    template<class T>
    using has_get = 
        decltype(std::declval<T&>().get(std::declval<int>()));

My question is where in the code should I add a static assert (or smthg else) to check the existence of the "get" function. Ideally, this should be part of C declaration, as things should be easy for new user code inheriting from it. It may also be that a completely different approach would be better, which I'd like to hear.

Upvotes: 1

Views: 523

Answers (1)

Yamahari
Yamahari

Reputation: 2038

Not sure what standard you are using but with C++20 you can do something like this using concepts

template<typename T>
concept HasGet = requires (T a)
{
  a.get();
};

template<HasGet T>
void foo(T x)
{
  x.get();
}

struct Foo
{
  int get() {
    return 1;
  }
};

struct Bar
{
};

int main()
{
  foo(Foo{});
  foo(Bar{});
}

Error:

<source>: In function 'int main()':

<source>:27:12: error: use of function 'void foo(T) [with T = Bar]' with unsatisfied constraints

   27 |   foo(Bar{});

      |            ^

<source>:8:6: note: declared here

    8 | void foo(T x)

      |      ^~~

<source>:8:6: note: constraints not satisfied

<source>: In instantiation of 'void foo(T) [with T = Bar]':

<source>:27:12:   required from here

<source>:2:9:   required for the satisfaction of 'HasGet<T>' [with T = Bar]

<source>:2:18:   in requirements with 'T a' [with T = Bar]

<source>:4:9: note: the required expression 'a.get()' is invalid

    4 |   a.get();

EDIT: As C++14 is preferred, if I understand you requirements, this is something you can do in C++14

#include <type_traits>
#include <utility>

using namespace std;

template<typename... Ts>
using void_t = void;

template<typename T, typename = void>
struct has_get
  : false_type
{};

template<typename T>
struct has_get<T, void_t<decltype(declval<T>().get())>>
  : true_type
{};

template<typename T>
static constexpr auto has_get_v = has_get<T>::value;

struct P
{

};

struct C1 : P
{
  int get()
  {
    return 1;
  }
};

struct C2 : P
{
  float get()
  {
    return 1.0F;
  }
};

struct C3
{
  bool get()
  {
    return true;
  }
};

template<typename T>
enable_if_t<is_base_of<P, decay_t<T>>::value && has_get_v<decay_t<T>>> foo(T x)
{
  x.get();
}

int main()
{
  foo(C1{});
  foo(C2{});
  foo(C3{});
}

ERROR:

<source>: In function 'int main()':

<source>:61:11: error: no matching function for call to 'foo(C3)'

   61 |   foo(C3{});

      |           ^

<source>:52:77: note: candidate: 'template<class T> std::enable_if_t<(std::is_base_of<P, typename std::decay<_Tp>::type>::value && has_get<typename std::decay<_Tp>::type>::value)> foo(T)'

   52 | enable_if_t<is_base_of<P, decay_t<T>>::value && has_get<decay_t<T>>::value> foo(T x)

      |                                                                             ^~~

<source>:52:77: note:   template argument deduction/substitution failed:

In file included from <source>:1:

/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]':

<source>:52:77:   required by substitution of 'template<class T> std::enable_if_t<(std::is_base_of<P, typename std::decay<_Tp>::type>::value && has_get<typename std::decay<_Tp>::type>::value)> foo(T) [with T = C3]'

<source>:61:11:   required from here

/opt/compiler-explorer/gcc-10.1.0/include/c++/10.1.0/type_traits:2554:11: error: no type named 'type' in 'struct std::enable_if<false, void>'

 2554 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;

Upvotes: 1

Related Questions