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 about Controller

    ,
  • is not required to be Controller

    inferred from any base class that provides default implementations, because the default method implementation will have sensitive information in Frontend

    .

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 implement supportsFeature

    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 for std::true_type

    vs std::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?
+3


source to share


2 answers


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 */ }
};

      

+1


source


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

      

+1


source







All Articles