The components class is boost.fusion.map-like class with constant set of keys (names) that supports default values for each key.
This class is used in the design of template function, that instead of taking several arguments takes one components class representing these arguments (see Motivating example for more details). One can think of the components class as a tuple containing these arguments.
This way of designing a template function provides many benefits. The arguments are binded together, this feature is valuable when the function arguments depend on each other. While the arguments are grouped together into one components class, one can easily replace one of them (see Replacing Components for more details).
There are more benefits of this design. Assume that one is implementing a template function and wants to provide several configurations of the default arguments and permits the function's user to easily create the new configuration of the defaults arguments. This can be easily done using the components class (see Motivating example for more details).
Suppose that we would like to write a function which takes three functors: Init, Start and Stop. We are going to construct an interface with usage of the components class.
Very basic usage:
The interface of the function has to be defined in the following way:
template <typename DoStuffComponents> void do_stuff(DoStuffComponents comps);
we need to define the names of the needed components:
struct Init; struct Start; struct Stop;
and specify the appropriate components class:
template <typename... Args> using DoStuffComponents = Components<Init, Start, Stop>::type<Args...>;
In order to use the function, the user needs to implement InitImpl, StartImpl, StopImpl functors. Then the function can be called as follows:
DoStuffComponents<InitImpl, StartImpl, StopImpl> doStuffComponents; do_stuff(doStuffComponents);
Here, we have assumed that all the implementations have default constructors, however, this is not mandatory. There are different ways of initializing components (see section: Constructing Components for more details).
Let us now discuss how to access the parameters in DoStuffComponets:
doStuffComponents.get<Init>(); //getting Init component
InitImpl anotherImplementation(42); doStuffComponents.set<Init>(anotherImplementation); //setting Init component
doStuffComponents.call<Start>("hello world"); // you can directly call a component if it is a functor
How to provide default parameters:
You can define DoStuffComponents as follows:
template <typename... Args> using DoStuffComponents = Components<Init, NameWithDefault<Start, DefaultStart>, NameWithDefault<Stop, DefaultStop>>::type<Args...>;
A user can construct the DoStuffComponents as follows:
DoStuffComponents<InitImpl> doStuffComponents1; DoStuffComponents<InitImpl, StartImpl> doStuffComponents2; DoStuffComponents<InitImpl, StartImpl, StopImpl> doStuffComponents3;
The above example illustrates the main motivation for the components class. The library also provides much more handy ways of manipulating the components, which will be described in the following sections.
A components can have any type including reference types. Default parameters can be specified for any number of components (when a component has a default value then also all following components must have a default value). The components class can be defined using template aliasing (this is the preferred way):
//inside library: template <typename... Args> using DoStuffComponents = Components<Init, Start, Stop>::type<Args...>;
//user: DoStuffComponents<InitImpl, BeginImpl, StopImpl> doStuffComponents;
This can also be done without template aliasing:
//inside library: typedef Components<Init, Start, Stop> DoStuffComponents;
//user: DoStuffComponents::type<InitImpl, BeginImpl, StopImpl> doStuffComponents;
There are several ways of constructing components:
By providing any number of arguments. The kth argument has to be convertible to the kth component:
//inside library: template <typename... Args> using DoStuffComponents = Components<Init, Start, Stop>::type<Args...>;
//user: typedef DoStuffComponents<double, int, int> MyDoStuffComponents;
MyDoStuffComponents doStuffComponents; MyDoStuffComponents doStuffComponents(1,2); int a; MyDoStuffComponents doStuffComponents(a);
By providing any object and a CopyTag. This tag indicates, that the passed object has get<Name> member functions for some Names:
template <typename... Args> using DoStuffComponents = Components<Init, Start, Stop>::type<Args...>; typedef DoStuffComponents<int, int, int> Big;
template <typename... Args> using SmallDoStuffComponents = Components<Start, Stop>::type<Args...>; typedef SmallDoStuffComponents<int, int> Small;
Small small(1,2); Big big(small, CopyTag());
Small small2(big, CopyTag());
Object can be made by providing some arguments by name:
template <typename... Args> using DoStuffComponents = Components<Init, Start, Stop>::type<Args...>; typedef DoStuffComponents<int, int, int> MyDoStuffComps;
auto m = MyDoStuffComps::make<Init, Stop>(7, 2); // The start component has default int value (actually as build in it might be uninitialized);
It is not necessary to provide any type at all for the components, as the following example shows:
typedef Components<Init, Start, Stop> DoStuffComponents;
int a; auto myComps = DoStuffComponents::make_components(1, a, std::ref(a));
If the deduced type should be a reference, the std::ref wraper should be used.
The important thing is that a component does not need to have a default constructor unless the default constructor is actually used.
The components can be replaced in a given components instance. A new instance is created this way:
template <typename... Args> using DoStuffComponents = Components<Init, Start, Stop>::type<Args...>; typedef DoStuffComponents<int, int, int> SomeDoStuffComps;
typedef ReplacedType<Start, double, SomeDoStuffComps>::type Replaced; // Start type is changed from int to double
SomeDoStuffComps comps; double d(5); Replaced replaced = replace<Start>(d, comps); //replacing component
Boost.Parameter
The main differences between boost.parameter and components classes are:
The components class is not designed to replace boost.parameter but in many cases (e.g., when the arguments are functors or when the given arguments can depend on each other) it might be a more natural design.
Boost.Fusion
Although the idea of the components is very similar to boost.fusion.map, the components gives some functionalities that are not offered by boost.fusion.map. For example, it supports different initialization methods (see section: Constructing Components for more details).
MSVC is not supported due to lack of template aliasing.