PaperBirdMaster
PaperBirdMaster

Reputation: 13320

Specialize template with function pointer, that depends on template parameter

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

Answers (3)

Shoe
Shoe

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();
};

Live demo

Upvotes: 2

Jarod42
Jarod42

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;

Live demo

Upvotes: 3

Oncaphillis
Oncaphillis

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

Related Questions