Use template option if available, default otherwise
I want to create a template class Frontend
for different Controller
s. Frontend
should use the implementation of the Controller
method if available (i.e. the function is supported) and defaults as in
template <typename Controller>
class Frontend
{
public:
something()
{
// use Controller::something() if possible
// else use default implementation
}
};
-
Frontend
will use type properties internally to learn more aboutController
, - is not required to be
Controller
inferred from any base class that provides default implementations, because the default method implementation will have sensitive information inFrontend
.
As Controller
will be realized about 20 methods. I tried to create (nested) traits that provide information about the provided implementations:
// (T: Controller class)
T::supportsFeature<FeatureClass, ...>::type
Means Controller::supportsFeature<>
to report on concrete implementations. ::type
std::true_type
if the controller supports the function or std::false_type
if it does not. I created a default framework for this purpose that disables any functionality, so the Controller class must explicitly include any functionality it provides. It seemed like a convenient way of transferring information from Controller
to Frontend
, but has two main disadvantages:
-
For others (who will eventually provide
Controller
) it is difficult to implementsupportsFeature
because the specialization of the nested traits class has to be written -
I'm not sure how I am supposed to convince the existence of a function in
Frontend::something()
, since no argument depends on the existence of that function - I cannot provide an overload that is expressive enough (I don't want to overload forstd::true_type
vsstd::false_type
, because that just doesn't speak for itself ). I could just use an if-statement and rely on the compiler to remove the dead code. Should I?
So, to summarize:
- How to transfer information about the existence of a method from a template class argument to a template class?
- How to properly switch between implementations based on this information?
source to share
I use this to verify the 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 */ }
};
source to share
After some try / error, here's what I would call an acceptable solution.
Using SFINAE, of course, all the inferences about whether to use a controller member function or a default function are done at compile time.
The only thing that developers of type Controllers need is to define the type within the controller as typedef Controller WithSomething;
. It's not difficult compared to the traits you talked about.
First, declare a template class Frontend
and define two template functions for each of the 20 called functions. 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 a function Frontend
with callable functions. Here I have defined the default implementations as a 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;
};
After all, some examples of classes Controller
. One that defines both foo
and bar
and two others that only define 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"; }
};
Usage Frondtend
with all of these classes and with all 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
source to share