Reputation: 688
I am trying to write mostly a reimplementation of std::array
. But, I am not very happy about the rules for aggregate initialization (and some other minor details) and thus do not want to use it.
Things I do not like with aggregate initilization:
struct A { int x,y; };
std::array< A, 2 > arr{1,2,3};
// produces A[0].x = 1, A[0].y = 2, A[1].x = 1, A[1].y = 0
class B { B() = delete; };
B{}; // no compile time error
What I have so far is the following:
#include <cstddef>
#include <type_traits>
#include <utility>
template< typename T, typename ... Ts >
inline constexpr bool areT_v = std::conjunction_v< std::is_same<T, Ts> ... >;
namespace ttt {
template< typename T, int N >
struct array
{
template< typename ... L >
array( L && ... lst ) : content{ std::forward< L >( lst ) ... } {
static_assert( sizeof...(L) == N || sizeof...(L)==0, "" );
static_assert( areT_v<T,L...>, "" );
}
array() = default; // not sure whether I need this one
using ContentType = typename std::conditional< N>=1 , T, char >::type;
ContentType content[ N>=1 ? N : 1];
/* member functions */
};
}
Actually I am quite happy with this implementation, apart from the following fact. Using
ttt::array
I have more ctor calls then when using std::array
. E.g.:
#include <array>
#include <iostream>
struct CLASS {
int x, y;
CLASS() {
std::cout << "def-ctor" << std::endl;
}
CLASS(int x_,int y_) : x(x_), y(y_) {
std::cout << "xy-ctor" << std::endl;
}
CLASS( CLASS const & other ) : x(other.x), y(other.y) {
std::cout << "copy-ctor" << std::endl;
}
};
int main() {
std::array< CLASS, 1 > stda{CLASS{1,2}}; // calls xy-ctor
ttt::array< CLASS, 1 > ttta{CLASS{1,2}}; // calls xy-ctor AND copy-ctor
}
This seems to be connected with the reason that std::array
is aggregate initialized (because it is an aggregate), but ttt::array
is not.
This leads me to the question, is the compiler allowed to do something here (omitting one ctor call), I am not allowed to do?
And, is there a way around this?
Upvotes: 9
Views: 387
Reputation: 117178
You could package the arguments that should be used to construct the elements in content
in std::tuple
s and delay creation of the actual objects until it's time to initialize content
.
An outline:
template <class T, std::size_t N>
struct array {
template<class... Ts>
constexpr array(Ts&&... args) :
content{to_t(std::forward<Ts>(args),
std::make_index_sequence<std::tuple_size<Ts>::value>())...}
{
static_assert(sizeof...(Ts) == N, "wrong number of elements");
}
private:
// create one T from a tuple
template<class... Ts, std::size_t... Idx>
T to_t(std::tuple<Ts...>&& in, std::index_sequence<Idx...>) {
return T{std::forward<Ts>(std::get<Idx>(in))...};
}
T content[N];
};
Example type to put in an array
:
struct Foo {
Foo() : a(0.), b(0) { std::cout << "Foo::Foo()\n"; }
explicit Foo(double A, int B) : a(A), b(B) { std::cout << "Foo::Foo(double, int)\n"; }
Foo(const Foo& o) : a(o.a), b(o.b) { std::cout << "Foo(const Foo&)\n"; }
Foo(Foo&& o) : a(o.a), b(o.b) { std::cout << "Foo(Foo&&)\n"; }
~Foo() { std::cout << "Foo::~Foo() {" << a << ", " << b << "}\n"; }
private:
double a;
int b;
};
You could then create your array
without copying/moving your T
:s (guaranteed in C++17, with copy/move elision in C++14, so not guaranteed but probable) by packaging them in tuple
s. It's a bit cumbersome to type. Hopefully someone else has an idea for how to get around that.
int main() {
Foo f{3.3, 3};
array<Foo, 4> x(std::forward_as_tuple(1.1, 1),
std::forward_as_tuple(), // default constructor
std::forward_as_tuple(2.2, 2),
std::forward_as_tuple(std::move(f)) // move constructor
);
}
Demo
You can add the compiler option -fno-elide-constructors
to see that it will move the elements when that optimization is turned off in C++14 mode.
Upvotes: 1