Jorge Bellon
Jorge Bellon

Reputation: 3096

Lazy initialization of std::tuple elements

I often use std::aligned_storage to specify an uninitialized class member. The typical example is an static_vector, which stores its elements within the structure.

However, I'm not completely sure what I should do when I want a std::tuple to be created step by step, initializing its members in an unspecified order at different points in time.

Would it be legal to create

std::tuple< std::aligned_storage<sizeof(Types),alignof(Types)>::type...>

and then reinterpret member reference as a std::tuple<Types...>&?

For example:

#include <bitset>
#include <memory>
#include <new>
#include <tuple>
#include <utility>

template < class... Ts >
class uninitialized_tuple {
    public:
        using tuple_type   = typename std::tuple<Ts...>;
        using buffer_type =
            std::tuple<
                typename std::aligned_storage<sizeof(Ts),alignof(Ts)>::type...
            >;

        ~uninitialized_tuple() {
            destruct_helper<std::index_sequence_for<Ts...>>::erase(*this);
        }

        tuple_type& as_tuple() {
            reinterpret_cast<tuple_type&>(_storage);
        }

        bool valid() const {
            return _is_set.all();
        }

        template < size_t index, class... Args >
        void emplace( Args&&... args ) {
            using element_type = typename std::tuple_element<index,tuple_type>::type;
            new (&std::get<index>(_storage)) element_type( std::forward<Args>(args)...);
            _is_set.set(index);
        }

        template < size_t index >
        void erase() {
            using element_type = typename std::tuple_element<index,tuple_type>::type;
            if( _is_set[index] ) {
                std::get<index>(_storage).~element_type();
                _is_set.reset(index);
            }
        }

    private:
        template < class Seq >
        struct destruct_helper {
            static void erase( uninitialized_tuple& ) {}
        };

        template < size_t index, size_t... indices >
        struct destruct_helper<std::index_sequence<index,indices...>> {
            static void erase( uninitialized_tuple& value ) {
                value.erase<index>();
                destruct_helper<std::index_sequence<indices...>>::erase_one(value);
            }
        };

        buffer_type                _storage;
        std::bitset<sizeof...(Ts)> _is_set;
};

Upvotes: 1

Views: 400

Answers (1)

Vittorio Romeo
Vittorio Romeo

Reputation: 93264

Accessing whatever is returned by as_tuple() is undefined behavior as it breaks the type aliasing rules. Please refer to https://en.cppreference.com/w/cpp/language/reinterpret_cast:

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

  • AliasedType and DynamicType are similar.

  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.

  • AliasedType is std::byte, (since C++17)char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

Upvotes: 2

Related Questions