Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parameterize the number of parameters of a constructor?

I want to accept up to a number of parameters (this number being defined in a template parameter) in a template class constructor. I can't use an initializer_list, since I can't assert its size at compile time, as far as I know.

What I tried

My first attempt was using a std::array as a parameter:

template<size_t s>
class foo {
  int v[s];
public:
  foo(std::array<int, s>) {/*...*/}
};

However, that forces me to initialize like this (even when the constructor is not explicit) :

foo<4> a{{1,2,3,4}} // Two brackets.

I would think there may be some template magic (variadic templates?), but I can't even figure out the proper syntax to use in the constructor. I can't call a constructor recursively... can I?

I tried looking for a definition of the constructor for std::array(since it doesn't allow for more parameters than the size of the array, just what I want), but all I could find is that it has implicit constructors. Is that the default constructors? If so, how does

std::array<int, 3> a = {1,2,3}

work?

Optional bonus: Why didn't the standard define a fixed size alternative to std::initializer_list? Something like std::static_initializer_list<T, N>. Are there any plans on supporting this kind of functionality in the future? Is it even needed?

like image 470
Cássio Renan Avatar asked Jun 18 '15 20:06

Cássio Renan


1 Answers

You could create a variadic constructor and just assert that it was provided the right number of arguments:

template <size_t SZ>
struct Foo {
    template <typename... Args>
    Foo(Args... args) {
        static_assert(sizeof...(Args) <= SZ, "Invalid number of arguments");
        // ... stuff ...
    }
};

So that:

Foo<3> f;                // OK
Foo<3> f(1, 2, 3);       // OK
Foo<3> f(1, 2, 3, 4, 5); // error

As an example to initialize an array, that could look like:

template <size_t SZ>
struct Foo {
    template <typename... Args>
    Foo(Args... args) 
    : v{{args...}}
    {
        static_assert(sizeof...(Args) <= SZ, "Invalid number of arguments");
    }

    std::array<int, SZ> v;
};

That constructs v correctly as you'd expect, though if you try to pass more than SZ args to Foo's constructor, you'd see the error on initializing v before the static_assert.

For a clearer static_assert error, you could delegate the top-level Foo to private constructors that take an extra integral_constant argument for whether or not they're valid constructors:

template <typename... Args>
Foo(Args... args)
: Foo(std::integral_constant<bool, sizeof...(Args) <= SZ>{}, 
      args...)
{ }

private:
template <typename... Args>
Foo(std::true_type, Args... args)
: v{{args...}}
{ }

template <typename False, typename... Args>
Foo(False, Args... )
{ 
    // False is only ever std::false_type
    static_assert(False::value, "Invalid number of arguments!");
}
like image 146
Barry Avatar answered Sep 23 '22 03:09

Barry