Reputation: 13320
I would like to have a template with a nested value which should be initialized by a given initializer function:
template <typename T, T(INIT)()> struct Foo
{
T value = INIT();
};
It can be used this way:
// Some random type only instanceable through factory()
struct Bar
{
int bar{};
private:
// The only way to create a Bar is through factory()
friend Bar factory();
Bar() {};
};
Bar factory() { return {}; }
Foo<Bar, factory> foo;
But, if no function is provided, the template should try to default-initialize the nested value, so I've tried to specialize the template:
template <typename T> struct Foo<T, nullptr>
{
T value{};
};
The idea is to use it this way:
struct Baz{};
Foo<Bar, factory> foo; // Nested Bar have Bar::bar initialized through factory function.
Foo<Baz> baz; // No factory function needed, nested Baz default-initialized.
But I just discovered that template partial specialization types cannot rely on other template types, the error I'm getting is pasted below:
error: type 'T (*)()' of template argument 'nullptr' depends on a template parameter template struct Foo
Is there a way to achieve my goal? It would be nice if it works with template variables as well:
template <typename T, T(INIT)()> T Foo = INIT();
template <typename T> T Foo<T, nullptr>{};
Extra question: Why partial specializations cannot depend on template parameters? What's the rationale behind this restriction?
Upvotes: 9
Views: 621
Reputation: 76240
You can define a constructor
template function that will initialize a value of type Type
and then use it as a default constructor:
template<typename Type, typename... Args>
Type constructor(Args... args) {
return Type(std::forward<Args>(args)...);
}
and then use it as default template argument for the function:
template <typename T, T(INIT)() = constructor<T>> struct Foo
{
T value = INIT();
};
Upvotes: 2
Reputation: 217265
For your case, you may use:
template <typename T>
T default_construct() { return T{}; }
template <typename T, T(INIT)() = &default_construct<T>>
struct Foo
{
T value = INIT();
};
And then use it like:
Foo<int> f;
Foo<int, bar> b;
Upvotes: 3
Reputation: 1908
If it's only about doing a default initialization if the second template parameter is missing, you may provide a templated default initialization function as the default parameter like.
template<typename T>
T do_default_assign() {
return T();
};
template <typename T, T (INIT)() = do_default_assign<T> > struct Foo
{
T value = INIT();
};
This however suffers an unnecessary "return by value" and assignment operation which might be costly or impossible for some T.
Upvotes: 2