Related
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
{
// ...
};
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.
suppose we have the following class in C++11 or later:
class MyClass {
private:
const std::array<SomeType, 100> myArray;
public:
explicit MyClass(std::array<SomeOtherType, 100> initArray);
};
Assuming that class SomeType has a constructor that takes a single SomeOtherType as an argument, is it possible to initialize the const member array using list-initialization in the constructor? What is the syntax for doing so?
Clearly, just directly initializing it like this doesn't work:
MyClass::MyClass(std::array<SomeOtherType, 100> initArray) :
myArray{initArray} {}
Thanks!
You can use a variadic template:
#include <array>
struct foo
{
const std::array<int, 10> bar;
template<typename... T>
foo(T&&... t)
: bar({ std::move(t)... })
{}
};
int main()
{
foo f{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
}
Or you can initialize it with an array passed to the constructor:
#include <array>
struct foo
{
const std::array<int, 10> bar;
explicit foo(std::array<int, 10> const &qux)
: bar{ qux }
{}
};
int main()
{
std::array<int, 10> qux;
foo f(qux);
}
But these options don't take into account that you want to have an array of SomeOtherType converted to an array of SomeType. I didn't realize that at first, hece the variants above.
#include <cstddef>
#include <array>
#include <utility>
struct SomeOtherType{};
struct SomeType {
SomeType(SomeOtherType) {}
};
struct MyClass
{
const std::array<SomeType, 100> myArray;
template<typename T, std::size_t... N>
MyClass(T&& qux, std::index_sequence<N...>)
: myArray{ qux[N]... }
{}
explicit MyClass(std::array<SomeOtherType, 100> const &qux)
: MyClass{ qux, std::make_index_sequence<100>{} }
{}
};
int main()
{
std::array<SomeOtherType, 100> qux{};
MyClass foo(qux);
}
You can unpack arguments with std::index_sequence and delegating constructors
template<typename Arr, size_t... Is>
MyClass(Arr&& arr, std::index_sequence<Is...>)
: myArray{arr[Is]...} ()
explicit MyClass(std::array<SomeOtherType, 100> arr) : MyClass(arr, std::make_index_sequence<100>{}) ()
This is possible. You just need a little helper function template to do the conversion for you. Something like this:
template <class T, class U, size_t N>
std::array<T, N> ArrayConvert(std::array<U, N> const& init)
{
std::array<T, N> result;
std::copy(init.begin(), init.end(), result.begin());
return result;
}
class Foo
{
std::array<int, 100> myArray;
public:
template <class U> Foo(std::array<U, 100> const& init)
: myArray(ArrayConvert<int>(init))
{
}
};
I'm trying to write a bit of code that requires me to have a lot of std::arrays in a container class. These arrays are all of varying sizes (all consecutive from 2-16, if that matters), and there is exactly one of each size. I want to put them in a container class, and be able to access them with templates.
It's probably easier to explain with code. I want something like this:
class ContainerClass {
public:
// I want to declare some number of arrays right here, all of different
// sizes, ranging from 2-16. I'd like to be able to access them as
// arr<2> through arr<16>.
// This code gives a compiler error, understandably.
// But this is what I'd think it'd look like.
template <size_t N> // I also need to find a way to restrict N to 2 through 16.
std::array<int, N> arr;
// An example method of how I want to be able to use this.
template <size_t N>
void printOutArr() {
for (int i = 0; i < N; i++) {
std::cout << arr<N>[i] << std::endl;
}
}
};
I'd like the code to expand out as if it just had 15 arrays in it, from 2-16. Like this, but with templates:
class ContainerClass {
public:
std::array<int, 2> arr2;
std::array<int, 3> arr3;
std::array<int, 4> arr4;
std::array<int, 5> arr5;
// ... and so on.
};
From what I understand, C++ supports variable templates, but it seems like it's only for static members in classes. Is there an alternative that could behave similarly (preferably with as little overhead as possible)?
If you need more information, please ask.
Thanks in advance.
Can I have non-static member variable templates?
No.
However, you can use templates to generate a list of members like you describe. Here is an example using recursive inheritance:
template<class T, std::size_t base, std::size_t size>
class Stair;
template<class T, std::size_t base>
class Stair<T, base, base> {};
template<class T, std::size_t base, std::size_t size>
class Stair : Stair<T, base, size - 1> {
protected:
std::array<T, size> arr;
public:
template<std::size_t s>
std::array<T, s>& array() {
return Stair<T, base, s>::arr;
}
};
int main()
{
Stair<int, 2, 10> s;
auto& arr = s.array<9>();
I think I may have a solution using recursive templates and std::tuple. I compiled and tested it using gcc 7.3.0.
This makes me feel dirty, but it seems to work.
#include <iostream>
#include <array>
#include <tuple>
#include <type_traits>
// Forward declare A since there is a circular dependency between A and Arrays
template <size_t size, size_t start, typename... T>
struct A;
// If size is greater than start define a type that is an A::ArrayTuple from the
// next step down (size - 1) otherwise type is void
template <size_t size, size_t start, typename E, typename... T>
struct Arrays {
using type = typename std::conditional<(size > start),
typename A<size-1, start, E, T...>::ArrayTuple,
void
>::type;
};
// Use template recursion to begin at size and define std::array<int, size>
// to add to a tuple and continue marching backwards (size--) until size == start
// When size == start take all of the std::arrays and put them into a std::tuple
//
// A<size, start>::ArrayTuple will be a tuple of length (size - start + 1) where
// the first element is std::array<int, start>, the second element is
// std::array<int, start + 1> continuing until std::array<int, size>
template <size_t size, size_t start, typename... T>
struct A {
using Array = typename std::array<int, size>;
using ArrayTuple = typename std::conditional<(size == start),
typename std::tuple<Array, T...>,
typename Arrays<size, start, Array, T...>::type
>::type;
};
// This specialization is necessary to avoid infinite template recursion
template <size_t start, typename... T>
struct A<0, start, T...> {
using Array = void;
using ArrayTuple = void;
};
template <size_t size, size_t start = 1>
class Container {
public:
using ArrayTuple = typename A<size, start>::ArrayTuple;
// Shorthand way to the get type of the Nth element in ArrayTuple
template <size_t N>
using TupleElementType = typename
std::tuple_element<N-start, ArrayTuple>::type;
ArrayTuple arrays_;
// Returns a reference to the tuple element that has the type of std::array<int, N>
template <size_t N>
TupleElementType<N>& get_array() {
// Static assertion that the size of the array at the Nth element is equal to N
static_assert(std::tuple_size< TupleElementType<N> >::value == N);
return std::get<N-start>(arrays_);
}
// Prints all elements of the tuple element that has the type of std::array<int, N>
template <size_t N>
void print_array() {
auto& arr = get_array<N>();
std::cout << "Array Size: " << arr.size() << std::endl;
for (int i = 0; i < arr.size(); i++) {
std::cout << arr[i] << std::endl;
}
}
};
int main() {
// Create a new Container object where the arrays_ member will be
// a tuple with 15 elements:
// std::tuple< std::array<int, 2>, std::array<int, 3> ... std::array<int, 16> >
Container<16,2> ctr;
auto& arr2 = ctr.get_array<2>();
arr2[0] = 20;
arr2[1] = 21;
//ctr.print_array<1>(); // Compiler error since 1 < the ctr start (2)
ctr.print_array<2>(); // prints 20 and 21
ctr.print_array<3>(); // prints 3 zeros
ctr.print_array<16>(); // prints 16 zeros
//ctr.print_array<17>(); // Compiler error since 17 > the ctr size (16)
//int x(ctr.arrays_); // Compiler error - uncomment to see the type of ctr.arrays_
return 0;
}
Here's the output from the compiler if I uncomment that line where I try to declare int x showing the type of ctr.arrays_:
so.cpp: In function ‘int main()’:
so.cpp:90:22: error: cannot convert ‘Container<16, 2>::ArrayTuple {aka std::tuple<std::array<int, 2>, std::array<int, 3>, std::array<int, 4>, std::array<int, 5>, std::array<int, 6>, std::array<int, 7>, std::array<int, 8>, std::array<int, 9>, std::array<int, 10>, std::array<int, 11>, std::array<int, 12>, std::array<int, 13>, std::array<int, 14>, std::array<int, 15>, std::array<int, 16> >}’ to ‘int’ in initialization
int x(ctr.arrays_); // Compiler error - uncomment to see the type of ctr.arrays_
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)<<" ";
}