Implementing std :: array-like constructors in other classes

In all modern C ++ compilers I have worked with, the following is legal:

std::array<float, 4> a = {1, 2, 3, 4}; 

I am trying to create my own class that has similar constructive semantics, but I am facing an annoying problem. Consider the following attempt:

 #include <array> #include <cstddef> template<std::size_t n> class float_vec { private: std::array<float, n> underlying_array; public: template<typename... Types> float_vec(Types... args) : underlying_array{{args...}} { } }; int main() { float_vec<4> v = {1, 2, 3, 4}; // error here } 

When using int literals like the one above, the compiler complains that it cannot implicitly convert int to float . I think it works in the std::array example, though, since the specified values ​​are compile-time constants, which are known to be in the float domain. Here, on the other hand, the variational pattern uses int for parameter types, and the conversion takes place in the list of constructor initializers, where the values ​​are unknown at compile time.

I do not want to do an explicit cast in the constructor, since then it will allow all numeric values, even if they cannot be represented by float .

The only thing I can think of to get what I want is to somehow have a variable number of parameters, but of a certain type (in this case, I would like a float ). I know std::initializer_list , but I would also like to provide the number of parameters at compile time.

Any ideas? Is what I want even with C ++ 11? Anything new suggested for C ++ 14 that solves this?

+7
c ++ arrays c ++ 11 variadic-templates
source share
4 answers

A little trick is to use constructor inheritance. Just make your class from another class that has a package of necessary parameters.

 template <class T, std::size_t N, class Seq = repeat_types<N, T>> struct _array_impl; template <class T, std::size_t N, class... Seq> struct _array_impl<T, N, type_sequence<Seq...>> { _array_impl(Seq... elements) : _data{elements...} {} const T& operator[](std::size_t i) const { return _data[i]; } T _data[N]; }; template <class T, std::size_t N> struct array : _array_impl<T, N> { using _array_impl<T, N>::_array_impl; }; int main() { array<float, 4> a {1, 2, 3, 4}; for (int i = 0; i < 4; i++) std::cout << a[i] << std::endl; return 0; } 

The following is an example implementation of the repeat_types utility. This example uses logarithmic recursion of a pattern, which is a little more intuitive to implement than with linear recursion.

 template <class... T> struct type_sequence { static constexpr inline std::size_t size() noexcept { return sizeof...(T); } }; template <class, class> struct _concatenate_sequences_impl; template <class... T, class... U> struct _concatenate_sequences_impl<type_sequence<T...>, type_sequence<U...>> { using type = type_sequence<T..., U...>; }; template <class T, class U> using concatenate_sequences = typename _concatenate_sequences_impl<T, U>::type; template <std::size_t N, class T> struct _repeat_sequence_impl { using type = concatenate_sequences< typename _repeat_sequence_impl<N/2, T>::type, typename _repeat_sequence_impl<N - N/2, T>::type>; }; template <class T> struct _repeat_sequence_impl<1, T> { using type = T; }; template <class... T> struct _repeat_sequence_impl<0, type_sequence<T...>> { using type = type_sequence<>; }; template <std::size_t N, class... T> using repeat_types = typename _repeat_sequence_impl<N, type_sequence<T...>>::type; 
+3
source share

First you see the default aggregation initialization . It exists from the earliest K & R C. If your type is an aggregate, it already supports aggregate initialization. In addition, your example will most likely compile, but the correct way to initialize it is std::array<int, 3> x ={{1, 2, 3}}; (note the double curly braces).

In C ++ 11, the initializer_list construct is added, which requires the implementation of compiler magic.

So now you can add constructors and assignment operators that take the value std::initializer_list , and this will offer the same syntax for your type.

Example:

 #include <initializer_list> struct X { X(std::initializer_list<int>) { // do stuff } }; int main() { X x = {1, 2, 3}; return 0; } 

Why is your current approach not working? Because in C ++ 11 std::initializer_list::size not constexpr or part of the initializer_list type. You cannot use it as a template parameter.

Several possible hacks: make your type aggregated.

 #include <array> template<std::size_t N> struct X { std::array<int, N> x; }; int main() { X<3> x = {{{1, 2, 3}}}; // triple braces required return 0; } 

Provide the make_* function to print the number of arguments:

 #include <array> template<std::size_t N> struct X { std::array<int, N> x; }; template<typename... T> auto make_X(T... args) -> X<sizeof...(T)> // might want to find the common_type of the argument pack as well { return X<sizeof...(T)>{{{args...}}}; } int main() { auto x = make_X(1, 2, 3); return 0; } 
+3
source share

If you initialize an instance with multiple brackets, you can use a different type of list-init to accept these transformations for compile-time constants. Here's the version that uses the raw array, so you only need brackets + brackets to build:

 #include <array> #include <cstddef> template<int... Is> struct seq {}; template<int N, int... Is> struct gen_seq : gen_seq<N-1, N-1, Is...> {}; template<int... Is> struct gen_seq<0, Is...> : seq<Is...> {}; template<std::size_t n> class float_vec { private: std::array<float, n> underlying_array; template<int... Is> constexpr float_vec(float const(&arr)[n], seq<Is...>) : underlying_array{{arr[Is]...}} {} public: constexpr float_vec(float const(&arr)[n]) : float_vec(arr, gen_seq<n>{}) {} }; int main() { float_vec<4> v0 ({1, 2, 3, 4}); // fine float_vec<4> v1 {{1, 2, 3, 4}}; // fine float_vec<4> v2 = {{1, 2, 3, 4}}; // fine } 
+1
source share

Explicitly indicate that the data type for initialization is a floating point type. You can do this by running "1.0f" instead of "1". If it is double, put "1.0d". If it is long, put "1l" and for unsigned long put "1ul", etc.

I tested it here: http://coliru.stacked-crooked.com/a/396f5d418cbd3f14 and here: http://ideone.com/ZLiMhg

Your code was ok. You just initialized the class a bit incorrectly.

 #include <array> #include <cstddef> #include <iostream> template<std::size_t n> class float_vec { private: std::array<float, n> underlying_array; public: template<typename... Types> float_vec(Types... args) : underlying_array{{args...}} { } float get(int index) {return underlying_array[index];} }; int main() { float_vec<4> v = {1.5f, 2.0f, 3.0f, 4.5f}; //works fine now.. for (int i = 0; i < 4; ++i) std::cout<<v.get(i)<<" "; } 
-one
source share

All Articles