I want to create a Frontend template class for different Controllers. The Frontend should use a Controller's implementation of a method if it is available (i.e. a feature is supported), and a default one otherwise, as in
template <typename Controller>
class Frontend
{
public:
something()
{
// use Controller::something() if possible
// else use default implementation
}
};
Frontend will use type traits internally to find out more about Controller,Controller to derive from any base class that provides default implementations, because the default method implementations will need information private to Frontend.There will be about 20 methods that might be implemented in Controller. I've tried to create (nested) traits that provide information about provided implementations:
// (T: Controller class)
T::supportsFeature<FeatureClass, ...>::type
So Controller::supportsFeature<> must be provided to inform about specific implementations. The ::type is std::true_type if the controller supports a feature or std::false_type if it doesn't. I've created a default struct for that purpose that disables any features, so the Controller class must explicitly enable any feature it provides. This seemed to be a comfortable way of carrying information from Controller to the Frontend, but it has two major drawbacks:
It's hard for others (who will eventually provide the Controller) to implement supportsFeature, because a specialization of a nested traits class must be written
I'm not sure how I should evaulate the existence of a feature in Frontend::something(), as no argument depends on the existence of that feature - I cannot provide an overload that is reasonably expressive (I don't want to overload for std::true_type vs std::false_type, because that simply doesn't speak for itself). I could simply use an if-statement and rely on compiler to remove dead code. Should I?
So, to sum it up:
I use that to check exact signature:
#include <cstdint>
#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature) \
template <typename U> \
class traitsName \
{ \
private: \
template<typename T, T> struct helper; \
template<typename T> \
static std::uint8_t check(helper<signature, &funcName>*); \
template<typename T> static std::uint16_t check(...); \
public: \
static \
constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
}
DEFINE_HAS_SIGNATURE(has_something, T::something, void (T::*)());
then use SFINAE (something like):
template <typename Controller>
class Frontend
{
public:
void something()
{
somethingT<Controller>();
}
private:
template <typename T>
typename std::enable_if<has_something<T>::value>::type
somethingT()
{
controller.something();
}
template <typename T>
typename std::enable_if<!has_something<T>::value>::type
somethingT()
{
// default implementation
}
};
or Tag dispatching (something like):
template <typename Controller>
class Frontend
{
public:
void something()
{
something(typename std::conditional<has_something<Controller>::value,
std::true_type,
std::false_type>::type());
}
private:
void something(std::true_type) { controller.something(); }
void something(std::false_type) { /* default implementation */ }
};
After some try/error, here's what I'd name an acceptable solution.
Using SFINAE, of course, all deductions about whether to use the Controller's member function or the default function are done at compile time.
The only thing required to the implementers of the Controllers type is to define a type within the controller as typedef Controller WithSomething;. It's not hard compared to the traits you mentionned.
First declare the Frontend template class, and define two template functions for every of your 20 callable functions. Here there are only two foo and bar.
#include <iostream>
using std::cout;
template <typename Ctrl>
class Frontend;
template <typename Ctrl>
void call_foo( typename Ctrl::WithFoo & ctrl ) { ctrl.foo(); }
template <typename Ctrl>
void call_foo( Ctrl & ctrl ) { Frontend<Ctrl>::default_foo( ctrl ); }
template <typename Ctrl>
void call_bar( typename Ctrl::WithBar & ctrl ) { ctrl.bar(); }
template <typename Ctrl>
void call_bar( Ctrl & ctrl ) { Frontend<Ctrl>::default_bar( ctrl ); }
Then define the Frontend function with the callable functions. Here I defined the default implementations as static member, but this can be changed.
template <typename Ctrl>
class Frontend
{
public:
typedef Ctrl controller;
void foo() { call_foo<Ctrl>( c ); }
void bar() { call_bar<Ctrl>( c ); }
static void default_foo( Ctrl & ctrl ) { cout<<"Default foo\n"; }
static void default_bar( Ctrl & ctrl ) { cout<<"Default bar\n"; }
private:
Ctrl c;
};
In the end, some examples of the Controller classes. One that defines both foo and bar, and two other that defines only one each.
struct CtrlFooBar
{
typedef CtrlFooBar WithFoo;
typedef CtrlFooBar WithBar;
void foo() { cout<<"CtrlFB foo\n"; }
void bar() { cout<<"CtrlFB bar\n"; }
};
struct CtrlFoo
{
typedef CtrlFoo WithFoo;
void foo() { cout<<"CtrlFoo foo\n"; }
};
struct CtrlBar
{
typedef CtrlBar WithBar;
void bar() { cout<<"CtrlBar bar\n"; }
};
Using the Frondtend with all these classes, and with an int works as expected.
int main()
{
Frontend<CtrlFooBar> c2;
Frontend<CtrlFoo> cf;
Frontend<CtrlBar> cb;
Frontend<int> ci;
c2.foo();
c2.bar();
cf.foo();
cf.bar();
cb.foo();
cb.bar();
ci.foo();
ci.bar();
return 0;
}
Output
CtrlFB foo
CtrlFB bar
CtrlFoo foo
Default bar
Default foo
CtrlBar bar
Default foo
Default bar
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With