Reputation: 598
I have a highly configurable class with many template parameters like this:
template<bool OptionA = false, bool OptionB = false, bool OptionC = false, class T = Class1B>
class MyClass
{
}
Now, if I want to create the class type and I only want to set OptionB to true, I have to do the following:
MyClass<false, true>
Especially for many template arguments, this get cumbersome.
No, my question is, is there any example available to create a template based class type by using the builder pattern?
I'm looking for something like this:
class Builder
{
useOptionA();
useOptionB();
useOptionC();
useClass2B(); //instead of Class1B
create();
}
Finally a call to Builder.useOptionB().useOptionC().useClass2B.create()
should return MyClass<false, true, true, Class2B>
. Is this possible?
Edit: Added class to template parameter list.
Upvotes: 9
Views: 4806
Reputation: 697
Here's the right way to implement this.
Suppose we have a following class template:
template<class TYPE_1, class TYPE_2, int value_1>
struct My_Class {
// ...
};
We want to be able to construct it this way:
auto object = My_Class_Builder::Value_1<42>::Type_2<float>::My_Class();
Builder implementation:
// specify default template arguments below:
template<class TYPE_1 = void, class TYPE_2 = void, int value_1 = 0>
struct _My_Class_Builder {
// equivalent of build() function. alternatively you can name it `Build`
using My_Class = ::My_Class<TYPE_1, TYPE_2, value_1>;
template<class NEW_TYPE_1>
using Type_1 = _My_Class_Builder<NEW_TYPE_1, TYPE_2, value_1>;
template<class NEW_TYPE_2>
using Type_2 = _My_Class_Builder<TYPE_1, NEW_TYPE_2, value_1>;
template<int new_value_1>
using Value_1 = _My_Class_Builder<TYPE_1, TYPE_2, new_value_1>;
};
// to make it possible to use without empty `<>`
using My_Class_Builder = _My_Class_Builder<>;
If you have My_Class
with some mandatory template argument, simply get rid of the last using My_Class_Builder = _My_Class_Builder<>;
declaration, and remove the _
from the main builder class name. Usage then becomes:
auto object = My_Class_Builder<double>::Value_1<42>::Type_2<float>::My_Class();
There's no runtime overhead, because no builder objects are created.
You can see this pattern in action in my triangle mesh manipulation library: Smesh_Builder
Upvotes: 2
Reputation: 275650
Consider a mpl-like dictionary.
template<class T>struct tag_t{using type=T; constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
template<class Tag>using type=typename Tag::type;
template<class K, class V, class...Base>
struct map_entry:Base... {
friend constexpr tag_t<V> get( tag_t<K>, map_entry ){ return {}; }
};
struct no_base{};
template<class Base, class...entries>
struct type_map;
template<class Base>
struct type_map<Base>:Base{};
template<class Base, class K0, class V0, class...B0s, class...Es>
struct type_map<Base, map_entry<K0,V0,B0s...>, Es...>:
map_entry<K0,V0, type_map<Base, B0s..., Es...>>
{};
template<class Map, class Key>
using lookup=type<decltype(get(tag<Key>,std::declval<Map>())>;
To pass integral constants like bool, use:
template<bool b>using bool_k=std::integral_constant<bool, b>;
integral_constant
s.
We define tags:
namespace MyTags{
struct OptionA{};
struct OptionB{};
struct OptionC{};
struct T{};
}
populate defaults:
using MyClassDefaults=type_map<
no_base,
map_entry< MyTags::OptionA, bool_k<false> >,
map_entry< MyTags::OptionB, bool_k<false> >,
map_entry< MyTags::OptionC, bool_k<false> >,
map_entry< MyTags::T, Class1B >
>;
template<class Options=MyClassDefaults>
class MyClass {
};
To read the values, do lookup<Options,MyTags::OptionA>
. Create a using
alias within the class to remove the need to repeat Options
.
To override, simply:
using Override=type_map<
MyClassDefaults,
map_entry< MyTags::OptionC, bool_k<true> >
>;
Code not tested.
Upvotes: 2
Reputation: 4339
As others have said, the easiest way to do what you want is with an enum
instead of a Builder. If, however, you do want a Builder, you can try something like this:
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
struct Builder_t
{
Builder_t() = default;
// ~Builder_t() { std::cout << "Builder dtor." << std::endl; }
auto useOptionA() -> Builder_t<true, OptionB, OptionC, T> { return {}; }
auto useOptionB() -> Builder_t<OptionA, true, OptionC, T> { return {}; }
auto useOptionC() -> Builder_t<OptionA, OptionB, true, T> { return {}; }
auto useClass2B() -> Builder_t<OptionA, OptionB, OptionC, Class2B> { return {}; }
MyClass<OptionA, OptionB, OptionC, T> create() { return {}; }
};
using Builder = Builder_t<>;
// ...
// Build MyClass<true, false, false, Class2B>:
auto ma2 = Builder{}.useOptionA().useClass2B().create();
This causes each function to return a distinct Builder
, whose template will be used by the next function; the final template is used as MyClass
' template. Each function modifies its specified template parameter, allowing a compile-time version of the Builder pattern. It does have a cost, though, which becomes apparent if the user-defined destructor is uncommented.
Consider this simple test program:
#include <iostream>
#include <typeinfo>
class Class1B {};
class Class2B {};
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
class MyClass
{
public:
MyClass() {
std::cout << "MyClass<"
<< OptionA << ", "
<< OptionB << ", "
<< OptionC << ", "
<< "type " << typeid(T).name() << ">"
<< std::endl;
}
};
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
struct Builder_t
{
Builder_t() = default;
// ~Builder_t() { std::cout << "Builder dtor." << std::endl; }
auto useOptionA() -> Builder_t<true, OptionB, OptionC, T> { return {}; }
auto useOptionB() -> Builder_t<OptionA, true, OptionC, T> { return {}; }
auto useOptionC() -> Builder_t<OptionA, OptionB, true, T> { return {}; }
auto useClass2B() -> Builder_t<OptionA, OptionB, OptionC, Class2B> { return {}; }
MyClass<OptionA, OptionB, OptionC, T> create() { return {}; }
};
using Builder = Builder_t<>;
int main()
{
std::cout << std::boolalpha;
std::cout << "Default:\n";
std::cout << "Direct: ";
MyClass<> m;
std::cout << "Builder: ";
auto mdefault = Builder{}.create();
std::cout << std::endl;
std::cout << "Builder pattern:\n";
std::cout << "A: ";
auto ma = Builder{}.useOptionA().create();
std::cout << "C: ";
auto mc = Builder{}.useOptionC().create();
std::cout << "---\n";
std::cout << "AB: ";
auto mab = Builder{}.useOptionA().useOptionB().create();
std::cout << "B2: ";
auto mb2 = Builder{}.useOptionB().useClass2B().create();
std::cout << "---\n";
std::cout << "ABC: ";
auto mabc = Builder{}.useOptionA().useOptionB().useOptionC().create();
std::cout << "AC2: ";
auto mac2 = Builder{}.useOptionA().useOptionC().useClass2B().create();
std::cout << "---\n";
std::cout << "ABC2: ";
auto mabc2 = Builder{}.useOptionA().useOptionB().useOptionC().useClass2B().create();
}
Normally, the output is as follows (using GCC):
Default:
Direct: MyClass<false, false, false, type 7Class1B>
Builder: MyClass<false, false, false, type 7Class1B>
Builder pattern:
A: MyClass<true, false, false, type 7Class1B>
C: MyClass<false, false, true, type 7Class1B>
---
AB: MyClass<true, true, false, type 7Class1B>
B2: MyClass<false, true, false, type 7Class2B>
---
ABC: MyClass<true, true, true, type 7Class1B>
AC2: MyClass<true, false, true, type 7Class2B>
---
ABC2: MyClass<true, true, true, type 7Class2B>
However, if we uncomment the destructor...
Default:
Direct: MyClass<false, false, false, type 7Class1B>
Builder: MyClass<false, false, false, type 7Class1B>
Builder dtor.
Builder pattern:
A: MyClass<true, false, false, type 7Class1B>
Builder dtor.
Builder dtor.
C: MyClass<false, false, true, type 7Class1B>
Builder dtor.
Builder dtor.
---
AB: MyClass<true, true, false, type 7Class1B>
Builder dtor.
Builder dtor.
Builder dtor.
B2: MyClass<false, true, false, type 7Class2B>
Builder dtor.
Builder dtor.
Builder dtor.
---
ABC: MyClass<true, true, true, type 7Class1B>
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
AC2: MyClass<true, false, true, type 7Class2B>
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
---
ABC2: MyClass<true, true, true, type 7Class2B>
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
Each call preceding Builder_t::create()
creates a distinct Builder_t
, all of which are subsequently destroyed after the instance is created. This can be mitigated by making Builder_t
a constexpr
class, but that can potentially slow compilation if there are a large number of parameters to deal with:
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
struct Builder_t
{
// Uncomment if you want to guarantee that your compiler treats Builder_t as constexpr.
// size_t CompTimeTest;
constexpr Builder_t()
// Uncomment if you want to guarantee that your compiler treats Builder_t as constexpr.
// : CompTimeTest((OptionA ? 1 : 0) +
// (OptionB ? 2 : 0) +
// (OptionC ? 4 : 0) +
// (std::is_same<T, Class2B>{} ? 8 : 0))
{}
constexpr auto useOptionA() -> Builder_t<true, OptionB, OptionC, T> { return {}; }
constexpr auto useOptionB() -> Builder_t<OptionA, true, OptionC, T> { return {}; }
constexpr auto useOptionC() -> Builder_t<OptionA, OptionB, true, T> { return {}; }
constexpr auto useClass2B() -> Builder_t<OptionA, OptionB, OptionC, Class2B> { return {}; }
constexpr MyClass<OptionA, OptionB, OptionC, T> create() { return {}; }
};
using Builder = Builder_t<>;
// ....
// Uncomment if you want to guarantee that your compiler treats Builder_t as constexpr.
// char arr[Builder{}/*.useOptionA()/*.useOptionB()/*.useOptionC()/*.useClass2B()/**/.CompTimeTest];
// std::cout << sizeof(arr) << '\n';
Upvotes: 7
Reputation: 12279
struct Builder
{
enum { A = 1, B = 2, C = 4 };
template<int f>
using MyClass_t = MyClass<f & A, f & B, f & C>;
template<int f>
static MyClass_t<f> create()
{ return MyClass_t<f>(); }
};
Example of use (C++11):
auto my_obj = Builder::create<Builder::A | Builder::B>();
Or instead of a create
method, if you want just faster access to the type:
Builder::MyClass_t<Builder::B> b;
If the opt_flag
is put outside the class, and you don't need a create
method, it's even shorter:
enum { optA = 1, optB = 2, optC = 4 };
template<int f>
using MyClass_t = MyClass<f & optA, f & optB, f & optC>;
int main()
{
MyClass_t<optA | optB> my_obj;
// ...
}
If only one of the template parameters is a typename
, add it to the using
declarator with the same default argument:
template<int f, class T = Class1B>
using MyClass_t = MyClass<f & optA, f & optB, f & optC, T>;
int main()
{
MyClass_t<optA | optB, std::string> my_obj;
MyClass_t<optB> my_obj_default_T;
// ...
}
Upvotes: 4
Reputation: 17704
Just use enum classes instead of bool.
enum class OptionA {TRUE, FALSE}
template <OptionA a> class Foo {};
Foo<OptionA::TRUE> f{};
Also, the builder pattern itself for templates, would only be possible by writing many intermediate types. Each building method has to return a type, along the way, and it can't be the final type. So for each function call, you'd have to write a new class template, and give the next function call as template function on that template class. So it would be strict with regards to order (unlike normal builder) and a lot of boilerplate.
Example:
template <bool OptA, bool OptB> class Bar {};
template <bool OptA>
struct OptASet {
template <bool OptB>
Bar<OptA, OptB> setOptB() { return Bar<OptA, OptB>{}; }
}
struct BarBuilder {
template <bool OptA>
setOptA() { return OptASet<OptA>{} }
}
auto b = BarBuilder.setOptA<true>().setOptB<false>();
As I said, it's strict with order, and you can't omit things, so it's much less useful than the builder pattern, esp when you have many defaulted arguments.
Upvotes: 0
Reputation: 62492
You won't be able to do that as you are trying to create an instance of something at runtime that needs to be defined at compile time. Think of it this way, what would be the return type of create()
? It would need to be MyClass<>
where you've specified the template parameters, but you are trying to do that at runtime, and they would actually need to be set at compile time.
Another option would be to make the options parameters to the constructor, rather than template parameters, and that way you can pass them in at construction.
class MyClass
{
public:
MyClass(bool optionA, bool optionB, bool optionC);
};
class Builder
{
private:
bool m_OptionA;
bool m_OptionB;
bool m_OptionC;
public:
Builder()
{
m_OptionA = false;
m_OptionB = false;
m_OptionC = false;
}
Builder &useOptionA()
{
m_OptionA = true;
return *this;
}
Builder &useOptionB()
{
m_OptionB = true;
return *this;
}
Builder &useOptionC()
{
m_OptionC = true;
return *this;
}
MyClass create() const
{
return MyClass(m_OptionA, m_OptionB, m_OptionC);
}
};
Now you can say:
MyClass instance = Builder().useOptionA().useOptionB().create();
Upvotes: 2