Back to Programming techniques
In this section we present a technique for making the template parameters of a class template robust to extensions, by using settings types.
The traditional way to write a class template is to list the template parameters explicitly:
template <typename Key_, typename Value_>
class RedBlackTree
{
public:
using Key = Key_;
using Value = Value_;
// ...
};
template <typename Key, typename Value>
void f(const RedBlackTree<Key, Value>& tree) {}
int main()
{
RedBlackTree<int, float> tree;
f(tree);
return 0;
}
This style has two disadvantages. First, since the function template f
assumes two template parameters for RedBlackTree
, the set of class template parameters for RedBlackTree
can not be extended without extending f
too, assuming f
is to work generically with all instances of RedBlackTree
. Second, when defining the member-functions of RedBlackTree
out of the class, the list of template parameters has to be repeated all over again.
The problems with the above technique can be solved by grouping the template parameters Key
and Value
into a single type Settings
. More precisely, the Settings
type models the RedBlackTree_Settings
concept associated with RedBlackTree
. We implicitly assume that the name of the concept that Settings
models is the class-name followed by _Settings
.
template <typename Settings>
class RedBlackTree
{
public:
using Key = typename Settings::Key;
using Value = typename Settings::Value;
// ...
};
template <typename Key_, typename Value_>
class RedBlack_Map_Settings
{
public:
using Key = Key_;
using Value = Value_;
// ...
};
template <typename Key_, typename Value_>
using RedBlack_Map = RedBlackTree<RedBlack_Map_Settings<Key_, Value_>>;
template <typename Key_>
using RedBlack_Set = RedBlackTree<RedBlack_Map_Settings<Key_, void>>;
template <typename Settings>
void f(const RedBlackTree<Settings>& tree) {}
int main()
{
RedBlack_Map<int, float> aTree;
f(aTree);
RedBlack_Set<float> bTree;
f(bTree);
return 0;
}
The user is not expected to write a settings class to use RedBlackTree
. Instead, the library writer provides the user with a set of alias templates, such as RedBlack_Map
and RedBlack_Set
, whose template parameters are given explicitly. Such an alias template captures a stable restricted subset of the settings. Any generic code is written for the RedBlackTree
, not for the alias templates, to capture all the possible extensions in the future.
It is now easy to extend the Settings
concept to capture more types and constants, without affecting existing code. Further, using SFINAE, it is possible to make it so that if the settings type does not contain a given type or a given constant, then it is assumed a default value. This makes the settings concept backwards-compatible, so that an old settings type can be passed instead of an extended settings types.
All of the data structures in Pastel fall into the following type-pattern:
template <
typename Settings,
template <typename> class Customization>
class DataStructure;
The second parameter is the customization, which can not be part of the settings. The settings-type can be recovered for such a type using the Settings_For
alias template. This is the recommended way to retrieve the settings-type, because it does not impose additional dependency-requirements for a type; it works even for an incomplete type.
// This recovers the Settings type.
using DataStructure_Settings = Settings_For<DataStructure<Settings, Customization>>;
The reason we haven’t used this technique before is that it requires support for alias templates, introduced in C++11, to avoid verbosity. Without the alias templates we would need to write RedBlackTree<RedBlack_Map_Settings<Key_, Value_>>
, which is unacceptable to us.