The C++ Concept-Model Idiom is also called Run-Time Concepts. Simply put, this idiom offers a unified interface as a concept that can be plugged in different models that fulfills that interface without direct inheritance.
And here is my understanding driven by a clear explaination on stackoverflow, and use its example as well.
Let's say, a callable object is an object which can be invoked like a function with arguments.
auto callable;
callable(args...);
First, how many kinds of callable object in C++? There are function, an object of the class with overloaded operator (), and lambda expression.
Second what are the types of them? For function, the type looks like ReturnType(Args); For an object of class, the type looks like T; and for lambda expression, the type is defined by compiler, and looks like regular class with overloaded operator (), T.
Therefore how can we unify all these types into a type named callable?
First of all, we need a wrapper to contain those callable objects.
Second, we need an interface to specify those objects are callable.
Third, we need a helper to facilitate the usage of them.
These three things are the major components of Concept-Model Idiom.
The wrapper is Model which implements the interface as Concept which specifies what it means.
Let us first see how to implement concept, then model, and finally the helper object as show in Stackoverflow.
#include <iostream>
#include <memory>
#include <type_traits>
#include <utility>
template <class function_type> struct function_concept;
template <class R, class... Args> struct function_concept<R(Args...)> {
// invoke:
virtual R operator()(Args...) const = 0;
// copy: ? why not use copy construtor and copy assignment ?
//virtual function_concept *clone() const = 0;
// destroy:
virtual ~function_concept() {}
};
template <class function_type, class Model> struct function_model;
template <class R, class... Args, class Model>
struct function_model<R(Args...), Model> : function_concept<R(Args...)> {
Model model;
// constructor:
function_model(Model t) : model(std::move(t)) {}
// invoke:
virtual R operator()(Args... args) const override {
return model(std::forward<Args>(args)...);
}
// copy:
// virtual function_concept *clone() const override {
// return new function_model{model};
// }
// destroy:
~function_model() = default;
};
template <class function_type> struct function;
template <class R, class... Args> struct function<R(Args...)> {
std::unique_ptr<function_concept<R(Args...)>> pImpl;
// invoke:
R operator()(Args... args) const {
return (*pImpl)(std::forward<Args>(args)...);
}
// destroy:
~function() = default;
// copy:
function(function const &o) : pImpl(o.pImpl) {}
// move:
function(function &&) = default;
function(function &o) : function(const_cast<function const &>(0)) {}
function(function const &&o) : function(o) {}
// type erase:
template <class T>
function(T &&t)
: pImpl{new function_model<R(Args...), std::decay_t<T>>{
std::forward<T>(t)}} {}
};
int main(int argc, char *argv[]) {
function<void()> f{[]() {std::cout << "hello world!\n";}};
f();
return 0;
}
Here we go, we just finished a handcraft std::function-like concept-model implementation.