std :: кортеж и стандартное расположение - PullRequest
13 голосов
/ 23 марта 2012

Если все члены std::tuple имеют стандартные типы макетов , является ли std::tuple стандартным макетом? Наличие определяемого пользователем конструктора копирования делает его нетривиальным, но мне было интересно, может ли оно все еще быть стандартным макетом.

Цитата из спецификации была бы хороша.

Ответы [ 4 ]

9 голосов
/ 23 марта 2012

Нет, стандартная компоновка требует, чтобы все нестатические элементы данных принадлежали либо одному базовому подобъекту, либо непосредственно самому производному типу, а типичные реализации std::tuple реализуют по одному члену на базовый класс.

Поскольку объявление члена не может быть расширением пакета, в свете вышеуказанного требования стандартная компоновка tuple не может иметь более одного члена. Реализация все еще может обойти проблему, храня все «1005 *« члены »внутри одного char[] и получая ссылки на объекты по reinterpret_cast. Метапрограмма должна была бы генерировать макет класса. Специальные функции-члены должны быть переопределены. Это было бы очень больно.

5 голосов
/ 28 ноября 2015

Вдохновленный ответом PotatoSwatter, я посвятил свой день созданию стандартного кортежа макетов для C ++ 14.

Код на самом деле работает, , но в настоящее время не подходит для использования, поскольку включает неопределенное поведение. Относитесь к этому как к доказательству концепции. Вот код, с которым я закончил:

#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;
}

Живой пример: https://ideone.com/4LEnSS

Есть несколько вещей, которыми я не доволен:

Это еще не подходит для использования «как есть», в действительности рассматривайте его только как подтверждение концепции в этом состоянии. Я, вероятно, вернусь, чтобы улучшить некоторые из этих вопросов. Или, если кто-то еще может улучшить его, не стесняйтесь редактировать.

0 голосов
/ 01 ноября 2018

Подход «список» можно использовать для получения стандартного макета tuple (следующий пример имеет некоторые неточности, но демонстрирует идею):

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 голосов
/ 28 ноября 2015

Одной из причин std::tuple не может быть стандартное расположение, так как любые классы с элементами и базовые классы с элементами, заключаются в том, что стандарт позволяет оптимизировать пространство при выводе даже непустых базовых классов. Например:

#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));
}

Выходы:

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

Выше показано, что стандарт допускает использование конечного заполнения класса для производных членов класса. Класс XY имеет два дополнительных члена uint16_t, но его размер равен размеру базового класса X.

Другими словами, макет класса XY такой же, как и у другого класса, у которого нет базовых классов и все члены XY упорядочены по адресу, например, struct XY2 { uint64_t a; uint32_t b; uint16_t c; uint16_t d; };.

Что делает его нестандартным макетом, так это то, что размер производного класса не зависит от размеров базовых классов и членов производного класса.

Обратите внимание, что размер struct / class кратен выравниванию одного из его элементов с наибольшим требованием выравнивания. Так что массив объектов соответствующим образом выровнен для такого члена. Для встроенных типов обычно sizeof(T) == alignof(T). Следовательно, sizeof(X) кратно sizeof(uint64_t).

Я не уверен, требует ли стандарт специальной обработки для struct, но с g++-5.1.1, если class заменен на struct, приведенный выше код дает другой вывод:

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

Другими словами, оптимизация пространства пробега не используется, когда задействован struct (не проверял точные условия).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...