Hide empty base class for aggregate initialization - c++

Consider the following code:
struct A
{
// No data members
//...
};
template<typename T, size_t N>
struct B : A
{
T data[N];
}
This is how you have to initialize B: B<int, 3> b = { {}, {1, 2, 3} };
I want to avoid the unnecessary empty {} for the base class.
There is a solution proposed by Jarod42 here, however, it doesn't work with elements default initialization: B<int, 3> b = {1, 2, 3}; is fine but B<int, 3> b = {1}; is not: b.data[1] and b.data[2] aren't default initialized to 0, and a compiler error occurs.
Is there any way (or there will be with c++20) to "hide" base class from construction?

The easiest solution is to add a variadic constructor:
struct A { };
template<typename T, std::size_t N>
struct B : A {
template<class... Ts, typename = std::enable_if_t<
(std::is_convertible_v<Ts, T> && ...)>>
B(Ts&&... args) : data{std::forward<Ts>(args)...} {}
T data[N];
};
void foo() {
B<int, 3> b1 = {1, 2, 3};
B<int, 3> b2 = {1};
}
If you provide fewer elements in the {...} initializer list than N, the remaining elements in the array data will be value-initialized as by T().

Since C++20 you could use designated initializers in aggregate initialization.
B<int, 3> b = { .data {1} }; // initialize b.data with {1},
// b.data[0] is 1, b.data[1] and b.data[2] would be 0

Still with constructor, you might do something like:
template<typename T, size_t N>
struct B : A
{
public:
constexpr B() : data{} {}
template <typename ... Ts,
std::enable_if_t<(sizeof...(Ts) != 0 && sizeof...(Ts) < N)
|| !std::is_same_v<B, std::decay_t<T>>, int> = 0>
constexpr B(T&& arg, Ts&&... args) : data{std::forward<T>(arg), std::forward<Ts>(args)...}
{}
T data[N];
};
Demo
SFINAE is done mainly to avoid to create pseudo copy constructor B(B&).
You would need extra private tag to support B<std::index_sequence<0, 1>, 42> ;-)

I've found another solution that (I don't know how) works perfectly and solves the problem we were discussing under Evg's answer
struct A {};
template<typename T, size_t N>
struct B_data
{
T data[N];
};
template<typename T, size_t N>
struct B : B_data<T, N>, A
{
// ...
};

Related

How would I get this functionality in my matrix class? [duplicate]

This question already has an answer here:
List initialization, Initializer_list and related questions in C++
(1 answer)
Closed 3 years ago.
#include <iostream>
#include <array>
template<typename T, std::size_t R, std::size_t C>
class matrix
{
std::array<T, R * C> m_data;
};
int main()
{
matrix<float, 2, 2> a = { 1,2,3,4 }; // COMPILER ERROR!
}
Clang reports that there is no matching constructor!
I have tried writing a constructor of the form
matrix(std::array<T,R*C> a);
and tried experimenting with && as I think the right-hand side of the expression in question is temporary. Which leads me to some confusion. As we would expect that it would be created then assigned(!) to the value of a.
Like others mentioned in the comments, you need a std::initializer_list<T> constructor for your matrix class.
#include <array> // std::array
#include <initializer_list> // std::initializer_list
#include <algorithm> // std::copy
#include <cassert>
template<typename T, std::size_t R, std::size_t C>
class matrix
{
std::array<T, R * C> m_data;
public:
matrix(const std::initializer_list<T> list)
// ^^^^^^^^^^^^^^^^^^^^^^^ --> constructor which takes std::initializer_list
{
assert(R * C == list.size());
std::copy(list.begin(), list.end(), m_data.begin());
}
};
int main()
{
matrix<float, 2, 2> a = { 1,2,3,4 }; // now compiles
}
(See live online)
However, that is not compile-time and come ups with the following drawbacks:
It does not check the passed types are same.
Size of the passed list can not be checked compile-time, as std::initializer_list is/can not be compile time.
Thirdly, it allows narrowing conversion.
In order to disallow the above, one solution is to provide a variadic template constructor which enables the above checks compile-time.
Something like as follows:(See live online)
#include <array> // std::array
#include <initializer_list> // std::initializer_list
#include <type_traits> // std::conjunction, std::is_same
#include <utility> // std::forward
// traits for checking the types (requires C++17)
template <typename T, typename ...Ts>
using are_same_types = std::conjunction<std::is_same<T, Ts>...>;
template<typename T, std::size_t R, std::size_t C>
class matrix
{
std::array<T, R * C> m_data;
public:
template<typename... Ts>
constexpr matrix(Ts&&... elemets) noexcept
{
static_assert(are_same_types<Ts...>::value, "types are not same!");
static_assert(sizeof...(Ts) == R*C, "size of the array does not match!");
m_data = std::array<T, R * C>{std::forward<Ts>(elemets)...};
}
};
int main()
{
matrix<float, 2, 2> a{ 1.f,2.f,3.f,4.f }; // now compiles
// matrix<float, 2, 2> a1{ 1,2,3,4 }; // error: narrowing conversion!
// matrix<float, 2, 2> a1{ 1.f,2.f,3.f, 4 }; // error: types are not same!
// matrix<float, 2, 2> a1{ 1.f,2.f,3.f,4.f, 5.f }; // error: size of the array does not match!
}
You don't need a std::initializer_list<T>.
One of it's drawbacks is that it doesn't check the number of arguments you pass it.
template<typename T, std::size_t R, std::size_t C>
class matrix
{
public:
matrix(const std::initializer_list<T> list) { /*...*/ }
};
int main()
{
matrix<float, 2, 2> a = { 1,2,3,4 }; // compiles
matrix<float, 2, 2> b = { 1,2,3,4,5 }; // also compiles
}
Instead, use the following:
template<typename T, std::size_t R, std::size_t C>
class matrix
{
std::array<T, R*C> m_data;
public:
matrix() = default;
template<typename... Us>
matrix(Us &&...args) : m_data{static_cast<T>(args)...}
{
}
};
int main()
{
matrix<float,2,2> a = {1,2,3}; // ok
matrix<float,2,2> b = {1,2,3,4}; // ok
matrix<float,2,2> c = {1,2,3,4,5}; // error
}
The cast is necessary to avoid a potential narrowing conversion.

How to check if template argument is a given value?

I cant figure out what this line should be:
class Mtx : public std::conditional<M==2 && N == 1, _Vec2<T>,
std::conditional<N == 1 && M == 3, _Vec3<T>, _MtxMN<T, M, N>>>
"M==2 && N == 1" is not valid code here I think.
Is what I am trying to do possible? If M=2 and N=1 I want to inherit from _Vec2, If M=3 and N=1 I want to inherit from _Vec3, otherwise from _MtxMN
This is so I can write code:
Vec2u v;
v.x=1;
Larger code snippet below:
template <typename T, std::size_t M, std::size_t N>
struct _MtxMN {
public:
std::array<T, M*N> v_;
};
template <typename T>
struct _Vec2 {
union {
struct { T x_, y_; };
struct { T width_, height_; };
struct { T cols_, rows_; };
std::array<T, 2> v_;
};
};
template <typename T>
struct _Vec3 {
union {
struct { T x_, y_, z_; };
struct { T width_, height_, depth_; };
std::array<T, 3> v_;
};
};
// M rows N columns
template <typename T, std::size_t M, std::size_t N>
class Mtx : public std::conditional<constexpr(M==2 && N == 1), _Vec2<T>, std::conditional<N == 1 && M == 3, _Vec3<T>, _MtxMN<T, M, N>>>
{
...
}
There are some syntax errors in your Mtx declaration. It should be
class Mtx : public std::conditional<M==2 && N == 1, _Vec2<T>, typename std::conditional<N == 1 && M == 3, _Vec3<T>, _MtxMN<T, M, N>>::type>::type
The underlying type of std::conditional needs to be refered with ::type, and for the second std::conditional we need to add a typename before it since it's a dependant name.
Also removed the constexpr() part, that's not needed and doesn't compile on gcc or clang.
An easily readable implementation (in my eyes) could go with partial template specialization:
template <typename T, std::size_t M, std::size_t N>
class Mtx : public _MtxMN<T, M, N>
{};
template <typename T>
class Mtx<T, 2, 1> : public _Vec2<T>
{};
template <typename T>
class Mtx<T, 3, 1> : public _Vec3<T>
{};
When the compiler looks for matches of your template instantiation, it will enforce the desired inheritance structure.
Mtx<int, 3, 6> mtx1; // instantiation that inherits from _MtxMN
Mtx<int, 2, 1> mtx2; // instantiation that inherits from _Vec2
Mtx<int, 3, 1> mtx3; // instantiation that inherits from _Vec3

Arity of aggregate in logarithmic time

How to define arity of an aggregate in logarithmic (at least base two) compilation time (strictly speaking, in logarithmic number of instantiations)?
What I can do currently is to achieve desired in a linear time:
#include <type_traits>
#include <utility>
struct filler { template< typename type > operator type (); };
template< typename A, typename index_sequence = std::index_sequence<>, typename = void >
struct aggregate_arity
: index_sequence
{
};
template< typename A, std::size_t ...indices >
struct aggregate_arity< A, std::index_sequence< indices... >, std::__void_t< decltype(A{(indices, std::declval< filler >())..., std::declval< filler >()}) > >
: aggregate_arity< A, std::index_sequence< indices..., sizeof...(indices) > >
{
};
struct A0 {};
struct A1 { double x; };
struct A2 { int i; char c; };
struct C50 { template< typename ...Args, typename = std::enable_if_t< (sizeof...(Args) < 51) > > C50(Args &&...) { ; } };
static_assert(aggregate_arity< A0 >::size() == 0);
static_assert(aggregate_arity< A1 >::size() == 1);
static_assert(aggregate_arity< A2 >::size() == 2);
static_assert(aggregate_arity< C50 >::size() == 50);
Live example.
Please correct me if term "arity" is poor.
I think it is possible in principle: firstly one need to double arity trials starting from one until SFINAE failed (surely, in soft manner), then use bisection.
A bit of terminology first: we can argue that you are not so much looking for the aggregate initialization arity but the maximum aggregate initialization arity. E.g. the aptly named A2 can be aggregate initialized from 0, 1, and 2 arguments so its maximum arity is 2.
Let’s turn 'is aggregate initializable from N arguments' into a trait (although with a shorter name):
struct filler { template<typename type> operator type () const; };
template<typename Arg> void accept(Arg);
template<typename Aggr, std::size_t... Indices,
typename = decltype( accept<Aggr>({ (static_cast<void>(Indices), filler {})... }) )>
void aggregate_arity_test(std::index_sequence<Indices...>);
template<typename Aggr, int N, typename Sfinae = void>
struct has_aggregate_arity: std::false_type {};
template<typename Aggr, int N>
struct has_aggregate_arity<Aggr, N, std::void_t<decltype( aggregate_arity_test<Aggr>(std::make_index_sequence<N>()) )>>
: std::true_type {};
(We use accept<Aggr>({ args... }) because that’s the same as checking for Aggr aggr = { args... };, i.e. copy-list-initialization and what people have in mind when they talk about aggregate initialization. Aggr aggr { args.. }; is direct-list-initialization, but you can still check against that if that’s what you care about.)
Now we can find an arity for which initialization fails in not too many instantiations with iterated doubling (i.e. we will check at arity 0, then arity 1, arity 2, arity 4, arity 8, ..., arity 2i):
template<typename Aggr, int Acc = 0>
struct find_failing_init_fast: std::conditional_t<
has_aggregate_arity<Aggr, Acc>::value,
find_failing_init_fast<Aggr, Acc == 0 ? 1 : Acc * 2>,
std::integral_constant<int, Acc>
> {};
Now it’s a matter of a binary search inside [0, N) where N is an arity for which initialization fails:
// binary search invariant:
// has_aggregate_arity<Aggr, Low> && !has_aggregate_arity<Aggr, High>
template<typename Aggr, int Low, int High>
struct max_aggregate_arity_impl
: std::conditional_t<
has_aggregate_arity<Aggr, midpoint(Low, High)>::value
&& !has_aggregate_arity<Aggr, midpoint(Low, High) + 1>::value,
std::integral_constant<int, midpoint(Low, High)>,
std::conditional<
has_aggregate_arity<Aggr, midpoint(Low, High)>::value,
max_aggregate_arity_impl<Aggr, midpoint(Low, High), High>,
max_aggregate_arity_impl<Aggr, Low, midpoint(Low, High)>
>
>::type {};
// special case that 'errors' out (important for SFINAE purposes)
// as the invariant obviously cannot be maintained
template<typename Aggr>
struct max_aggregate_arity_impl<Aggr, 0, 0> {};
template<typename Aggr>
struct max_aggregate_arity
: max_aggregate_arity_impl<Aggr, 0, find_failing_init_fast<Aggr>::value> {};
Live On Coliru
Discussion
(The discussion is based on another answer of mine which I will delete now.)
As in the original question, the following answer checks whether the invocation of the constructor of the aggregate is possible with a given number of arguments. For aggregates, one can base a binary search on this pattern by using the following properties from the standard:
8.5.1 (6):
An initializer-list is ill-formed if the number of initializer-clauses
exceeds the number of members or elements to initialize. [ Example:
char cv[4] = { ’a’, ’s’, ’d’, ’f’, 0 }; // error is ill-formed. — end
example ]
and
8.5.1 (7):
If there are fewer initializer-clauses in the list than there are
members in the aggregate, then each member not explicitly initialized
shall be initialized from its default member initializer (9.2) or, if
there is no default member initializer, from an empty initializer list
(8.5.4). [ Example: struct S { int a; const char* b; int c; int d =
b[a]; }; S ss = { 1, "asdf" }; initializes ss.a with 1, ss.b with
"asdf", ss.c with the value of an expression of the form int{} (that
is, 0), and ss.d with the value of ss.b[ss.a] (that is, ’s’), and in
struct X { int i, j, k = 42; }; X a[] = { 1, 2, 3, 4, 5, 6 }; X b[2] =
{ { 1, 2, 3 }, { 4, 5, 6 } }; a and b have the same value — end
example ]
However, as you already implied by the question title, a binary search will in general not work with non-aggregates, first due to the fact that those are usually not callable with less parameters than necessary, and next due to the fact that non-aggregates can have explicit constructors so that the "conversion-to-anything" trick via the struct filler won't work.
Implementation
First ingredient is an is_callable check from here:
template<typename V, typename ... Args>
struct is_constructible_impl
{
template<typename C> static constexpr auto test(int) -> decltype(C{std::declval<Args>() ...}, bool{}) { return true; }
template<typename> static constexpr auto test(...) { return false; }
static constexpr bool value = test<V>(int{});
using type = std::integral_constant<bool, value>;
};
template<typename ... Args>
using is_constructible = typename is_callable_impl<Args...>::type;
Note that this one is usable also with a fewer number of parameters than necessary (unlike your check).
Next a helper function which takes an integer argument and returns whether the aggregate is callable with the corresponding number of constructor arguments:
template<typename A, size_t ... I>
constexpr auto check_impl(std::index_sequence<I ...>)
{
return is_constructible<A, decltype(I, filler{}) ...>::value;
}
template<typename A, size_t N>
constexpr auto check()
{
return check_impl<A>(std::make_index_sequence<N>{});
}
And finally the binary search:
template<typename A, size_t Low, size_t Up, size_t i = Low + (Up - Low)/2>
struct binary_search
: public std::conditional_t<check<A, i>() && !check<A,i+1>()
, std::integral_constant<size_t, i>
, std::conditional_t<check<A, i>()
, binary_search<A, i, Up>
, binary_search<A, Low, i> >
>
{};
Use it as
int main()
{
static_assert(binary_search<A2,0,10>::value==2);
}
Live on Coliru

Class with nested brace-enclosed initializers

I want to create a recursive, multi-dimensional array class using variadic templates.
#include <array>
template<int...> struct multi;
template<int Body>
struct multi<Body>: std::array<int, Body> {
template<typename... Params>
multi(int first, const Params&... args)
: std::array<int, Body> {{ first, args... }} {
}
};
template<int Head, int Body, int... Tail>
struct multi<Head, Body, Tail...>: std::array<multi<Body, Tail...>, Head> {
template<typename... Params>
multi(const multi<Body, Tail...>& first, const Params&... args)
: std::array<multi<Body, Tail...>, Head> {{ first, args... }} {
}
};
To initialize an instance of this, class, I have to do the following:
multi<2, 2> m { multi<2>{ 1, 2 }, multi<2>{ 3, 4 } };
I'd really appreciate if I could just use a C-array-like style for uniform initialization like so:
multi<2, 2> m { { 1, 2 }, { 3, 4 } };
Unfortuanely, this doesn't work as it seems to be impossible to handle these nested initialiers:
multi.cc: In function 'int main()':
multi.cc:25:40: error: no matching function for call to 'multi<2, 2>::multi(<brace-enclosed initializer list>)'
multi<2, 2> m { { 1, 2 }, { 3, 4 } };
^
multi.cc:25:40: note: candidates are:
multi.cc:19:5: note: multi<Head, Body, Tail ...>::multi(const multi<Body, Tail ...>&, const Params& ...) [with Params = {}; int Head = 2; int Body = 2; int ...Tail = {}]
multi(const multi<Body, Tail...>& first, const Params&... args)
^
multi.cc:19:5: note: candidate expects 1 argument, 2 provided
multi.cc:17:8: note: constexpr multi<2, 2>::multi(const multi<2, 2>&)
struct multi<Head, Body, Tail...>: std::array<multi<Body, Tail...>, Head> {
^
multi.cc:17:8: note: candidate expects 1 argument, 2 provided
multi.cc:17:8: note: constexpr multi<2, 2>::multi(multi<2, 2>&&)
multi.cc:17:8: note: candidate expects 1 argument, 2 provided
Is there any way to achieve this?
You can get pretty close, if you don't mind multiple braces:
template<int...> struct multi;
template <int N>
struct multi<N> {
int elems[N];
};
template <int Head, int... Tail>
struct multi<Head, Tail...> {
multi<Tail...> elems[Head];
};
int main() {
multi<2, 2, 2> m {{ {{ {{ 1, 2 }}, {{ 3, 4 }} }}, {{ {{ 5, 6 }}, {{ 7, 8 }} }} }};
}
What I've done is removed the inheritance and the custom constructor, which makes it possible to get this working with the compiler's own support for aggregate initialisation.
The fact that multiple braces are needed is unfortunate, but I don't see an easy way of avoiding that, and it doesn't hurt that much for readability, in my opinion.
A slightly more complicated solution, but still not very complicated, is to not nest multi<...>, but to use a multidimensional array:
template<int...> struct helper;
template<int N>
struct helper<N> { typedef int type[N]; };
template<int Head, int... Tail>
struct helper<Head, Tail...> { typedef typename helper<Tail...>::type type[Head]; };
template<int... Ns>
struct multi {
typename helper<Ns...>::type elems;
};
int main() {
multi<2> m1 { 1, 2 };
multi<2, 2> m2 {{ { 1, 2 }, { 3, 4 } }};
multi<2, 2, 2> m3 {{ { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }};
}
You do still need multiple braces, but only at the outer level.
If it turns out you actually don't need a class at all, though, it can be done:
template<int...> struct helper;
template<int N>
struct helper<N> { typedef int type[N]; };
template<int Head, int... Tail>
struct helper<Head, Tail...> { typedef typename helper<Tail...>::type type[Head]; };
template<int... Ns>
using multi = typename helper<Ns...>::type;
int main() {
multi<2> m1 { 1, 2 };
multi<2, 2> m2 { { 1, 2 }, { 3, 4 } };
multi<2, 2, 2> m3 { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } };
}
But as multi<2, 2, 2> is now merely int[2][2][2], you cannot add any methods (or anything similar) any more.
You are trying to initialize your class like an aggregate, but it isn't an aggregate because it has a user-provided constructor.
You can remove the user-defined constructors and make your class an aggregate, as hvd explains. Or, you can make your constructor take std::initializer_list. However, this approach has some caveats...
#include <array>
#include <initializer_list>
#include <algorithm>
template<int...> struct multi;
template<int Body>
struct multi<Body>: std::array<int, Body> {
multi() = default;
multi(std::initializer_list<int> l) {
std::copy(l.begin(), l.end(), std::array<int, Body>::begin());
}
};
template<int Head, int Body, int... Tail>
struct multi<Head, Body, Tail...>: std::array<multi<Body, Tail...>, Head> {
multi() = default;
multi(std::initializer_list<multi<Body, Tail...>> l) {
for (int i = 0; i < Head; i++) {
(*this)[i] = multi<Body, Tail...>(l.begin()[i]);
}
}
};
int main() {
multi<2, 2> m {{1, 2}, {3, 4}}; // OK
multi<2, 1000> m2 {{1, 2}, {3, 4}}; // constructs temporary arrays of size 1000 and copies them---expensive!
multi<2> m3 {1, 2, 3}; // too many initializers, but no compile error!
}
The issue with unnecessary copying can be resolved by passing the initializer list in its original form, a (possibly) nested initializer list of ints, and iterating at each level. But in this case template parameter deduction doesn't work and you need a helper class. Also, you still can't trigger a compile error when too many initializers are given.
#include <array>
#include <initializer_list>
#include <algorithm>
// IL<n>::type is an n-times nested initializer list of ints
template<int n> struct IL {
typedef std::initializer_list<typename IL<n-1>::type> type;
};
template<> struct IL<1> {
typedef std::initializer_list<int> type;
};
template<int...> struct multi;
template<int Body>
struct multi<Body>: std::array<int, Body> {
multi() = default;
multi(const std::initializer_list<int>& l) {
assign(l);
}
void assign(const std::initializer_list<int>& l) {
std::copy(l.begin(), l.end(), std::array<int, Body>::begin());
}
};
template<int Head, int Body, int... Tail>
struct multi<Head, Body, Tail...>: std::array<multi<Body, Tail...>, Head> {
multi() = default;
multi(const typename IL<2 + sizeof... Tail>::type& l) {
assign(l);
}
void assign(const typename IL<2 + sizeof... Tail>::type& l) {
for (int i = 0; i < l.size(); i++) {
(*this)[i].assign(l.begin()[i]);
}
}
};

Implementing std::array-like constructors in other classes

In all the modern C++ compilers I've worked with, the following is legal:
std::array<float, 4> a = {1, 2, 3, 4};
I'm trying to make my own class that has similar construction semantics, but I'm running into 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 above, the compiler complains it can't implicitly convert int to float. I think it works in the std::array example, though, because the values given are compile-time constants known to be within the domain of float. Here, on the other hand, the variadic template uses int for the parameter types and the conversion happens within the constructor's initializer list where the values aren't known at compile-time.
I don't want to do an explicit cast in the constructor since that would then allow for all numeric values even if they can't be represented by float.
The only way I can think of to get what I want is to somehow have a variable number of parameters, but of a specific type (in this case, I'd want float). I'm aware of std::initializer_list, but I'd like to be able to enforce the number of parameters at compile time as well.
Any ideas? Is what I want even possible with C++11? Anything new proposed for C++14 that will solve this?
A little trick is to use constructor inheritance. Just make your class derive from another class which has a pack of the parameters you want.
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;
}
Here is a sample implementation of the repeat_types utility. This sample uses logarithmic template recursion, which is a little less 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;
First of what you are seeing is the default aggregate initialization. It has been around since the earliest K&R C. If your type is an aggregate, it supports aggregate initialization already. Also, 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 braces).
What has been added in C++11 is the initializer_list construct which requires a bit of compiler magic to be implemented.
So, what you can do now is add constructors and assignment operators that accept a value of 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 does your current approach not work? Because in C++11 std::initializer_list::size is not a constexpr or part of the initializer_list type. You cannot use it as a template parameter.
A few possible hacks: make your type an aggregate.
#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 a make_* function to deduce 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;
}
If you use several braces to initialize the instance, you can leverage list-init of another type to accept these conversions for compile-time constants. Here's a version that uses a raw array, so you only need parens + braces for construction:
#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
}
Explicitly specify that the data type for the initialization to floating point type. You can do this by doing "1.0f" instead of putting "1". If it is a double, put "1.0d". If it is a long, put "1l" and for unsigned long put "1ul" and so on..
I've tested it here: http://coliru.stacked-crooked.com/a/396f5d418cbd3f14
and here: http://ideone.com/ZLiMhg
Your code was fine. You just initialized the class a bit incorrect.
#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)<<" ";
}