views:

69

answers:

2

I'd like to pass some numeric byte values via an initializer list a variadic template into an array. Is that possible?

template < int N > struct a {
  char s[N];
  template < typename ... A >
  a (A ... _a) : s {_a...} {}
};

int main () {
  // g++-4.5: error: narrowing conversion of »_a#0« from »int« to »char« inside { }
  a < 3 > x { 1, 2, 3 };
}

What I can think of is

  • to use octal representation, '\001' etc., or
  • to cast every single value.

But both is not satisfying.

A: 

You're not actually using initializer lists. The constructor receives a variadic template and you initialize x with uniform initialization.

The only problem is I don't know of an elegant way of initializing an array with an initializer_list, AFAIK std::array should have a constructor that accepts initializer_list but it doesn't seem to be supported by g++ yet.

#include <utility>
template < int N > struct a {
    char s[N];

    a (std::initializer_list<char> list) {
        if (N != list.size()) 
            throw "list wrong size";

        int i = 0;
        const char* p = list.begin();
        while(p != list.end())
            s[i++] = *p++;
    }
};
Motti
You're absolutely right, variadic templates is what I meant to write. Initializer lists are perhaps not as efficient for array initialization.
Thomas
`array` doesn't need to support `initializer_list` because it already does the same thing in C++03 by being an aggregate. Nothing is more efficient than that, as it amounts to in-place memberwise initialization.
Potatoswatter
+1  A: 

NOTE: All of this is unnecessary unless you have added functionality to the class so it's no longer an aggregate. (For example, other constructors, private members, a base class, etc.) The immediate way to fix the code in the question is simply to remove the constructor. So, let's assume there's something more to it.

I've seen some people trying to do things like this. It seems ugly, dealing with conversion semantics and trying to artificially re-create the functionality of a usual function call.

Here is a strategy to create an array class that simply has the right constructor in the first place.

Template aliasing would put the icing on the cake by hiding the ::type ugliness, but it's not in GCC yet.

template< typename ... NT >
struct var_ctor_array {
    enum { size_e = 0 }; // only used for zero size case
};

template< typename T, typename ... NT >
struct var_ctor_array< T, NT ... > {
    enum { size_e = 1 + sizeof...( NT ) };

    T st[ size_e ];

    var_ctor_array( T elem0, NT ... elemN )
        : st { elem0, elemN ... } {}
};

template< typename T, size_t N, typename ... NT >
struct gen_var_ctor_array {
    typedef typename gen_var_ctor_array< T, N-1, T, NT ... >::type type;
};

template< typename T, typename ... NT >
struct gen_var_ctor_array< T, 0, NT ... > {
    typedef var_ctor_array< NT ... > type;
};

int main() { // usage
    gen_var_ctor_array< char, 5 >::type five( 1, 2, 3, 4, 5 );
}
Potatoswatter
Awesome idea, I'll use the classes. g++ won't compile without the special case for `size_e == 0` due to a bug. Now, would it be possible to have `T()` as default value for every parameter?
Thomas
@Thomas: Trying it out, "parameter pack ‘elemN’ cannot have a default argument". However, that feature could be hacked using inheriting constructors, which aren't in GCC 4.5.
Potatoswatter
What triggers that bug? `gen_var_ctor_array< char, 0 >::type zero;` works…
Potatoswatter
I meant: Leaving away the first four lines (because I won't have arrays of size 0) and removing the template list in line 7 would cause g++ to fail. (But of course, adding the special case is better, anyway).
Thomas
@Thomas: Ah, yes, that's an integral part. The comment "only used" is just referring to the one line, inside the braces.
Potatoswatter