std::tuple and standard layout

前端 未结 4 2362
感情败类
感情败类 2021-02-19 05:10

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-constr

相关标签:
4条回答
  • 2021-02-19 05:29

    One reason std::tuple cannot be of standard layout, as any classes with members and base classes with members, is that the standard allows for space optimization when deriving even non-empty base classes. For example:

    #include <cstdio>
    #include <cstdint>
    
    class X
    {
        uint64_t a;
        uint32_t b;
    };
    
    class Y
    {
        uint16_t c;
    };
    
    class XY : public X, public Y
    {
        uint16_t d;
    };
    
    int main() {
        printf("sizeof(X) is %zu\n", sizeof(X));
        printf("sizeof(Y) is %zu\n", sizeof(Y));
        printf("sizeof(XY) is %zu\n", sizeof(XY));
    }
    

    Outputs:

    sizeof(X) is 16
    sizeof(Y) is 2
    sizeof(XY) is 16
    

    The above shows that the standard allows for class trailing padding to be used for the derived class members. Class XY has two extra uint16_t members, yet its size equals to the size of base class X.

    In other words, class XY layout is the same as that of another class that has no base classes and all the members of XY ordered by address, e.g. struct XY2 { uint64_t a; uint32_t b; uint16_t c; uint16_t d; };.

    What makes it non-standard layout is that the size of a derived class is not a function of sizes of base classes and derived class members.

    Note that the size of a struct/class is a multiple of the alignment of one of its members with the largest alignment requirement. So that an array of objects is suitably aligned for such a member. For built-in types normally sizeof(T) == alignof(T). Hence sizeof(X) is a multiple of sizeof(uint64_t).

    I am not sure whether the standard requires special treatment for struct, but with g++-5.1.1 if class is replaced with struct the above code yields different output:

    sizeof(X) is 16
    sizeof(Y) is 2
    sizeof(XY) is 24
    

    In other words, the trailing padding space optimization is not used when struct is involved (did not test for exact conditions).

    0 讨论(0)
  • 2021-02-19 05:35

    The "list" approach can be used to get standard layout tuple (the following example has some inaccuracies but demonstrates the idea):

    template <class... Rest>
    struct tuple;
    
    template <class T, class... Rest>
    struct tuple<T, Rest...> {
        T value;
        tuple<Rest...> next;
    };
    
    template <>
    struct tuple<> {};
    
    namespace details {
        template <size_t N>
        struct get_impl {
            template <class... Args>
            constexpr static auto process(const tuple<Args...>& t) {
                return get_impl<N - 1>::process(t.next);
            }
        };
    
        template <>
        struct get_impl<0> {
            template <class... Args>
            constexpr static auto process(const tuple<Args...>& t) {
                return t.value;
            }
        };
    }
    
    template <size_t N, class... Args>
    constexpr auto get(const tuple<Args...>& t) {
        return details::get_impl<N>::process(t);
    }
    
    template <class... Args>
    constexpr auto make_tuple(Args&&... args) {
        return tuple<Args...>{std::forward<Args>(args)...};
    }
    
    0 讨论(0)
  • 2021-02-19 05:40

    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.

    0 讨论(0)
  • 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.

    0 讨论(0)
提交回复
热议问题