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.
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;
};