I've been looking for decent discussions of CRTP myself. Todd Veldhuizen's Techniques for Scientific C++ is a great resource for this (1.3) and many other advanced techniques like expression templates.
Also, I found that you could read most of Coplien's original C++ Gems article at Google books. Maybe that's still the case.
The second one is by avoiding the use of the reference-to-base or pointer-to-base idiom and do the wiring at compile-time. Using the above definition, you can have template functions that look like these:
template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
obj.foo(); // will do static dispatch
}
struct not_derived_from_base { }; // notice, not derived from base
// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload
So combining the structure/interface definition and the compile-time type deduction in your functions allows you to do static dispatch instead of dynamic dispatch. This is the essence of static polymorphism.
CRTP/SFINAE Static Dispatching with Strict Signature Checking
This solution for static dispatching uses CRTP and SFINAE, which is not new.
What is unique about this solution is that it also enforces strict signature
checking, which allows us to statically dispatch overloaded methods in the same
way dynamic dispatching works for virtual functions.
To begin, let's first look at the limitations of a traditional solution using
SFINAE. The following was taken from Ben Deane's CppCon 2016 Lightning Talk
“A Static Alternative to Virtual Functions, Using Expression SFINAE."
Using the above code, the template instantiation has_complete<DerivedClass>
will, in general, do what you would expect. If DerivedClass has a method named
Complete that accepts a std::string, the resulting type will be
std::true_type.
What happens when you want to overload a function?
In this case, Derived does, in fact, have a method named foo that accepts a
bool because bool is implicitly convertible to int. Therefore,
even if we only set up dispatching for the signature that accepts a bool, has_foo<Derived> would resolve to std::true_type, and the call would be
dispatched to Derived::foo(int). Is this what we want? Probably not, because
this is not the way that virtual functions work. A function can only override a
virtual function if the two signatures match exactly. I propose that we make a
static dispatch mechanism that behaves in the same way.
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;
That's nice, but that alone doesn't enforce signature checks. To perform strict
signature checking, we have to properly define the template template parameter
Op. To do this, we will make use of a std::integral_constant of a member
function pointer. Here's what that looks like:
template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;
template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>
Defining our Ops in this way allows us to dispatch only to methods with an
exact signature match.
// Resolves to std::integral_constant<std::string(T::*)(bool), &Derived::foo>
using foo_bool_ic = dispatcher_t<foo_op_b, Derived, Defaults>;
// Resolves to std::integral_constant<std::string(T::*)(int), &Defaults::foo>
using foo_int_ic = dispatcher_t<foo_op_i, Derived, Defaults>;
Now let's put it all together.
#include <iostream>
#include <experimental/type_traits>
#include <string>
template <template <class...> class Op, class... Types>
struct dispatcher;
template <template <class...> class Op, class T>
struct dispatcher<Op, T> : std::experimental::detected_t<Op, T> {};
template <template <class...> class Op, class T, class... Types>
struct dispatcher<Op, T, Types...>
: std::experimental::detected_or_t<
typename dispatcher<Op, Types...>::type, Op, T> {};
template <template <class...> class Op, class... Types>
using dispatcher_t = typename dispatcher<Op, Types...>::type;
// Used to deduce class type from a member function pointer
template <class R, class T, class... Args>
auto method_cls(R(T::*)(Args...)) -> T;
struct Defaults {
std::string foo(bool value) { return value ? "true" : "false"; }
std::string foo(int value) { return value ? "true" : "false"; }
// Ensure that the class is polymorphic so we can use dynamic_cast
virtual ~Defaults() {};
};
template <class Derived>
struct Base : Defaults {
template <class T>
using foo_op_b = std::integral_constant<std::string(T::*)(bool), &T::foo>;
template <class T>
using foo_op_i = std::integral_constant<std::string(T::*)(int), &T::foo>;
std::string foo(bool value) {
auto method = dispatcher_t<foo_op_b, Derived, Defaults>::value;
auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
return (target->*method)(value);
}
std::string foo(int value) {
auto method = dispatcher_t<foo_op_i, Derived, Defaults>::value;
auto *target = dynamic_cast<decltype(method_cls(method)) *>(this);
return (target->*method)(value);
}
};
struct Derived : Base<Derived> {
std::string foo(bool value) { return value ? "TRUE" : "FALSE"; }
};
int main() {
Derived d;
std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(true) << std::endl; // TRUE
std::cout << dynamic_cast<Base<Derived> *>(&d)->foo(1) << std::endl; // true
}
Writing a macro that creates a dispatcher for a non-overloaded member function
would be simple enough, but making one that supports overloaded functions would
be a bit more challenging. If anybody cares to contribute that, I'd welcome the
addition.