Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use template argument's implementation if available, default otherwise

Tags:

c++

templates

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,
  • it should not be required for 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:

  1. 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

  2. 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:

  • How do I carry information about the existence of a method from a template class argument to the template class?
  • How do I properly switch between implementations, based on that information?
like image 394
Christoph Avatar asked Mar 23 '26 18:03

Christoph


2 Answers

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 */ }
};
like image 135
Jarod42 Avatar answered Mar 26 '26 08:03

Jarod42


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
like image 32
Didier Trosset Avatar answered Mar 26 '26 09:03

Didier Trosset



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!