I'm working on a library where client code can define certain types using CRTP. I've extracted the problem into the following code:
template <typename T, typename X>
struct A
{
void test(X x)
{
return static_cast<T &>(*this).test(std::forward<X>(x));
}
};
struct B: public A<B, int>
{
void test(int x){};
};
int main(void)
{
B b;
b.test(5);
};
I want to have the compiler validate that client-defined types (struct B
in the example code) provide the required interface. The above code with segfault if test
is not defined on B
. I wrote a type trait, api_defines_test<T>
that can check if the test
member function is defined. I realize now that this won't work: the trait is satisfied by the test
member function in the base class, and this approach also requires me to know the concrete type of the user-defined classes which obviously isn't possible. I'm not asking how I can assert on types that are not yet defined, but is there some clever way to get a similar effect?
You can place a static_assert
in the A::test
method where you have access to T
(which is the user defined CRTP type). You can apply whatever type constraints you need at that point even though the CRTP type has not been defined yet -- that is a feature of CRTP.
The methods in A
should have different names than their corresponding implementations in B
as mentioned by @vvv444 in the comments. In the canonical CRTP pattern, A
provides the interface and B
the implementation.
#include <iostream>
using std::cout, std::endl;
template <typename T, typename X>
struct A {
void test(X x) {
static_assert(std::is_member_function_pointer_v<decltype(&T::test_impl)>);
return static_cast<T &>(*this).test_impl(std::forward<X>(x));
}
};
struct B: public A<B, int> {
void test_impl(int x){};
};
int main(void) {
B b;
b.test(5);
};
If you do not declare B::test_impl
, the static_assert
will trigger. Of course, in this case the assert is redundant because you will get a compilation error without the assert. This just demonstrates the ability to apply type constraints to the CRTP type in A
.
src/p1.cpp:11:70: error: no member named
'test_impl' in 'B'
static_assert(std::is_member_function_pointer_v<decltype(&T::test_impl)>);
~~~^
src/p1.cpp:22:7: note: in instantiation of
member function 'A<B, int>::test' requested here
b.test(5);