Search code examples
c++templatesdesign-patternspolicy

Policy Based Design - Conditional use of Base class (functions)


In the hello world example of policy based design from wikipedia we have this very nice way of defining the implementation (policy-) specific member functions via using directives:

template <typename OutputPolicy, typename LanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy {
public:
// Behavior method.
void Run() const {
   // Two policy methods.
   Print(Message());
}

private:
using LanguagePolicy::Message;
using OutputPolicy::Print;
};

Now assume that you want to allow a user to forget implementing the Message method in the language policy - there can be many others the user did implement. Hence, you define a fallback class

class DefaultLanguagePolicy {
protected:
  std::string Message() const { return ""; }
  // and fallbacks for other methods a user may not have implemented
}

But how do you enable this default if Message is not implemented in the HelloWorld Class and discard it otherwise?

template <typename OutputPolicy, typename LanguagePolicy, typename DefaultLanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy, private DefaultLanguagePolicy {
public:
// Behavior method.
 void Run() const {
   // Two policy methods.
   Print(Message());
 }

 private:
 // what do I need to write to get this kind of behaviour:
 if (LanguagePolicy has Message implemented) {
    using LanguagePolicy::Message;
 } else {
    using DefaultLanguagePolicy::Message;
 }
   using OutputPolicy::Print;
 };

Thanks for any suggestions that ideally allow to keep this nice using syntax.


Solution

  • If it can be set design decision in the project, that every policy derives from default-policy - like:

    class SomeLangaugePolicy : public DefaultLanguagePolicy { ... };
    

    Then there is nothing to be changed in class HelloWorld - I mean the first version of this class.


    Otherwise - use tools from <type_traits>:

    Detecting if the given class has Message function is possible by using std::void_t

    template <typename T, typename = std::void_t<>> 
    struct has_message : std::false_type {};
    template <typename T> 
    struct has_message<T, std::void_t<decltype(&T::Message)>> : std::true_type {};
    

    With this has_message trait, with std::conditional_t is possible to select type depending on Message presence:

    template <typename LanguagePolicy, typename DefaultLanguagePolicy>
    using LanguageBase = std::conditional_t<has_message<LanguagePolicy>::value,
                                            LanguagePolicy, 
                                            DefaultLanguagePolicy>;
    

    And needed modifications in your class:

    template <typename OutputPolicy, typename LanguagePolicy, typename DefaultLanguagePolicy>
    class HelloWorld : 
             private OutputPolicy, 
             private LanguageBase<LanguagePolicy, DefaultLanguagePolicy> {
    public:
        // Behavior method.
        void Run() const {
            // Two policy methods.
            Print(Message());
        }
    
    private:
        using LanguageBase<LanguagePolicy, DefaultLanguagePolicy>::Message;
        using OutputPolicy::Print;
    };
    
    

    Working Demo

    Now it is easy to do the same with OutputPolicy and all other policies.

    You might define macro to define policy detectors:

    #define DEFINE_HAS_FUNCTION_TRAIT(function) \
    template <typename T, typename = std::void_t<>> \ 
    struct has_##function : std::false_type {}; \
    template <typename T> \
    struct has_##function<T, std::void_t<decltype(&T::function)>> : std::true_type {}
    
    

    Then:

    DEFINE_HAS_FUNCTION_TRAIT(Message); // define has_Message
    DEFINE_HAS_FUNCTION_TRAIT(Print); // define has_Print
    

    To handle case when there are more functions in the given Policy than just one - use this approach:

    template <typename OutputPolicy, typename LanguagePolicy, typename DefaultLanguagePolicy>
    class HelloWorld : 
             private OutputPolicy, 
             private LanguagePolicy, private DefaultLanguagePolicy {
    public:
        // Behavior method.
        void Run() const {
            // Two policy methods.
            Print(Message());
        }
    
    private:
        using std::conditional_t<has_Message<LanguagePolicy>::value,
                                 LanguagePolicy, 
                                 DefaultLanguagePolicy>::Message;
        using OutputPolicy::Print;
    };