tommsch
tommsch

Reputation: 688

Aggregate like Initialization without superfluous calls of constructors

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:

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

Answers (1)

Ted Lyngmo
Ted Lyngmo

Reputation: 117178

You could package the arguments that should be used to construct the elements in content in std::tuples 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 tuples. 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

Related Questions