Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::tuple and standard layout

If all of the members of std::tuple are of standard layout types, is that std::tuple itself standard layout? The presence of a user-defined copy-constructor makes it non-trivial, but I was wondering if it can still be standard layout.

A quote from the spec would be good.

like image 247
Nicol Bolas Avatar asked Mar 23 '12 03:03

Nicol Bolas


People also ask

What is a std :: tuple?

Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair.

Is std :: pair a tuple?

A tuple is an object capable to hold a collection of elements where each element can be of a different type. The class template needs the header <tuple> . std::tuple is a generalization of std::pair. You can convert between tuples with two elements and pairs.

Is tuple a Constexpr?

Initializes each element of the tuple with the corresponding element of other . This constructor is constexpr if every operation it performs is constexpr. For the empty tuple std::tuple<>, it is constexpr.

What is the difference between tuple and pair?

The Tuple is an object capable to hold a collection of elements, where each element can be of different types. The pair can make a set of two values, which may be of different types. The pair is basically a special type of tuple, where only two values are allowed.


2 Answers

No, standard layout requires that all nonstatic data members belong to either one base subobject or directly to the most derived type, and typical implementations of std::tuple implement one member per base class.

Because a member-declaration cannot be a pack expansion, in light of the above requirement, a standard layout tuple cannot have more than one member. An implementation could still sidestep the issue by storing all the tuple "members" inside one char[], and obtaining the object references by reinterpret_cast. A metaprogram would have to generate the class layout. Special member functions would have to be reimplemented. It would be quite a pain.

like image 78
Potatoswatter Avatar answered Oct 31 '22 19:10

Potatoswatter


Inspired by PotatoSwatter's answer, I've dedicated my day to creating a standard layout tuple for C++14.

The code actually works, but is not currently suited for use as it involves undefined behaviour. Treat it as a proof-of-concept. Here's the code I ended up with:

#include <iostream>
#include <type_traits>
#include <array>
#include <utility>
#include <tuple>

//get_size
template <typename T_head>
constexpr size_t get_size()
{
    return sizeof(T_head);
}

template <typename T_head, typename T_second, typename... T_tail>
constexpr size_t get_size()
{
    return get_size<T_head>() + get_size<T_second, T_tail...>();
}


//concat
template<size_t N1, size_t... I1, size_t N2, size_t... I2>
constexpr std::array<size_t, N1+N2> concat(const std::array<size_t, N1>& a1, const std::array<size_t, N2>& a2, std::index_sequence<I1...>, std::index_sequence<I2...>)
{
  return { a1[I1]..., a2[I2]... };
}

template<size_t N1, size_t N2>
constexpr std::array<size_t, N1+N2> concat(const std::array<size_t, N1>& a1, const std::array<size_t, N2>& a2)
{
    return concat(a1, a2, std::make_index_sequence<N1>{}, std::make_index_sequence<N2>{});
}


//make_index_array
template<size_t T_offset, typename T_head>
constexpr std::array<size_t, 1> make_index_array()
{
    return {T_offset};
}

template<size_t T_offset, typename T_head, typename T_Second, typename... T_tail>
constexpr std::array<size_t, (sizeof...(T_tail) + 2)> make_index_array()
{
    return concat(
        make_index_array<T_offset, T_head>(),
        make_index_array<T_offset + sizeof(T_head),T_Second, T_tail...>()
    );
}

template<typename... T_args>
constexpr std::array<size_t, (sizeof...(T_args))> make_index_array()
{
    return make_index_array<0, T_args...>();
}


template<int N, typename... Ts>
using T_param = typename std::tuple_element<N, std::tuple<Ts...>>::type;


template <typename... T_args>
struct standard_layout_tuple
{
    static constexpr std::array<size_t, sizeof...(T_args)> index_array = make_index_array<T_args...>();

    char storage[get_size<T_args...>()];

    //Initialization
    template<size_t T_index, typename T_val>
    void initialize(T_val&& val)
    {
        void* place = &this->storage[index_array[T_index]];
        new(place) T_val(std::forward<T_val>(val));
    }

    template<size_t T_index, typename T_val, typename T_val2, typename... T_vals_rest>
    void initialize(T_val&& val, T_val2&& val2, T_vals_rest&&... vals_rest)
    {
        initialize<T_index, T_val>(std::forward<T_val>(val));
        initialize<T_index+1, T_val2, T_vals_rest...>(std::forward<T_val2>(val2), std::forward<T_vals_rest>(vals_rest)...);
    }

    void initialize(T_args&&... args)
    {
        initialize<0, T_args...>(std::forward<T_args>(args)...);
    }

    standard_layout_tuple(T_args&&... args)
    {
        initialize(std::forward<T_args>(args)...);
    }

    //Destruction
    template<size_t T_index, typename T_val>
    void destroy()
    {
        T_val* place = reinterpret_cast<T_val*>(&this->storage[index_array[T_index]]);
        place->~T_val();
    }

    template<size_t T_index, typename T_val, typename T_val2, typename... T_vals_rest>
    void destroy()
    {
        destroy<T_index, T_val>();
        destroy<T_index+1, T_val2, T_vals_rest...>();
    }

    void destroy()
    {
        destroy<0, T_args...>();
    }

    ~standard_layout_tuple()
    {
        destroy();
    }

    template<size_t T_index>
    void set(T_param<T_index, T_args...>&& data)
    {
        T_param<T_index, T_args...>* ptr = reinterpret_cast<T_param<T_index, T_args...>*>(&this->storage[index_array[T_index]]);
        *ptr = std::forward<T_param<T_index, T_args...>>(data);
    }

    template<size_t T_index>
    T_param<T_index, T_args...>& get()
    {
        return *reinterpret_cast<T_param<T_index, T_args...>*>(&this->storage[index_array[T_index]]);
    }
};


int main() {
    standard_layout_tuple<float, double, int, double> sltuple{5.5f, 3.4, 7, 1.22};
    sltuple.set<2>(47);

    std::cout << sltuple.get<0>() << std::endl;
    std::cout << sltuple.get<1>() << std::endl;
    std::cout << sltuple.get<2>() << std::endl;
    std::cout << sltuple.get<3>() << std::endl;

    std::cout << "is standard layout:" << std::endl;
    std::cout << std::boolalpha << std::is_standard_layout<standard_layout_tuple<float, double, int, double>>::value << std::endl;

    return 0;
}

Live example: https://ideone.com/4LEnSS

There's a few things I'm not happy with:

  • Alignment is not handled properly which means entering misaligned types will currently give you undefined behaviour
  • Not all tuple functionality is represented yet.
  • I don't think the memory management is currently exception-safe.
  • It uses tuple to determine the type for each index.
  • The overall code quality is kinda messy.
  • There might be better, more concise ways to handle some of the recursive template functions.
  • I don't fully understand everything I did. I understand the main basics, but I'm using some of the weirder syntax on good faith. Here's the most important sources I used:
    • Create N-element constexpr array in C++11
    • Lookup table with constexpr
    • c++11 constexpr flatten list of std::array into array
    • constexpr, arrays and initialization
    • template parameter packs access Nth type and Nth element

This is not yet suitable for use as-is, really only treat it as a proof-of-concept in this state. I will probably come back to improve on some of these issues. Or, if anyone else can improve it, feel free to edit.

like image 32
Aberrant Avatar answered Oct 31 '22 17:10

Aberrant