2012-03-23 5 views
10

Если все элементы std::tuple изготовлены из standard layout types, то std::tuple сам по себе стандартный макет? Наличие пользовательского конструктора-копии делает его нетривиальным, но мне было интересно, может ли он по-прежнему быть стандартным макетом.std :: кортеж и стандартная компоновка

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

+0

Если вы хотите знать это, потому что вам нужна возможность оптимизации, вы должны использовать 'std :: is_standard_layout' и взять ветвь времени компиляции. Тогда вы можете отдохнуть, зная, что вы оптимальны, не зная всех деталей самого типа. – GManNickG

+0

Кажется, что это было бы, но я не могу найти упоминания о стандартном макете в разделе стандарта, который охватывает 'tuple'. Может быть упоминание где-то еще, но если так, я еще не нашел его. –

ответ

8

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

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

+1

Типичные реализации, да, но не _good_ реализации ([согласно Говарду] (http://stackoverflow.com/a/9643480/636019): -P); Я подозреваю, что они потребовали, чтобы кортежи стандартных типов были стандартными. – ildjarn

+0

@ildjarn см. Отредактированный ответ – Potatoswatter

+0

См. Отредактированный комментарий. ; -] – ildjarn

3

Вдохновленный ответом 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

Одна из причин, по которой 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 (не проверял точные условия).

 Смежные вопросы

  • Нет связанных вопросов^_^