2012-03-23 13 views
10

Si todos los miembros de std::tuple son de standard layout types, ¿ese es el diseño estándar de std::tuple? La presencia de un constructor de copias definido por el usuario no lo hace trivial, pero me preguntaba si todavía puede ser un diseño estándar.std :: tuple y diseño estándar

Una cita de la especificación sería buena.

+0

Si desea saber esto porque desea una oportunidad de optimización, debe usar 'std :: is_standard_layout' y tomar una rama en tiempo de compilación. Entonces puede descansar sabiendo que está siendo óptimo sin conocer todos los detalles del tipo en sí. – GManNickG

+0

Parece que sí, pero no puedo encontrar ninguna mención de 'layout estándar' en la sección del estándar que cubre' tuple'. Puede haber una mención en otro lugar, pero si es así, aún no la he encontrado. –

Respuesta

8

No, el diseño estándar requiere que todos los miembros de datos no estáticos pertenezcan a un subobjeto base o directamente al tipo más derivado, y las implementaciones típicas de std::tuple implementan un miembro por clase base.

Como una declaración de miembro no puede ser una expansión de paquete, a la luz del requisito anterior, un diseño estándar tuple no puede tener más de un miembro. Una implementación aún podría eludir el problema almacenando todos los tuple "miembros" dentro de un char[], y obteniendo las referencias de objeto por reinterpret_cast. Un metaprograma debería generar el diseño de clase. Las funciones especiales de los miembros tendrían que ser reimplementadas. Sería un gran dolor.

+1

Implementaciones típicas, sí, pero no _good_ implementaciones ([según Howard] (http://stackoverflow.com/a/9643480/636019) :-P); Sospecho que podrían haber requerido que las tuplas de los tipos de diseño estándar solo sean de diseño estándar ... – ildjarn

+0

@ildjarn ver respuesta editada – Potatoswatter

+0

Ver el comentario editado. ; -] – ildjarn

3

Inspirado por la respuesta de PotatoSwatter, he dedicado mi día a crear una tupla de diseño estándar para C++ 14.

El código realmente funciona, pero no es actualmente adecuado para su uso, ya que implica un comportamiento indefinido. Trátelo como una prueba de concepto. Aquí está el código que terminó con:

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

ejemplo vivo: https://ideone.com/4LEnSS

Hay algunas cosas que no estoy feliz con:

Esto todavía no es adecuado para su uso como está, Realmente solo lo trato como una prueba de concepto en este estado. Probablemente regrese para mejorar algunos de estos problemas.O, si alguien más puede mejorarlo, siéntete libre de editarlo.

0

Una razón por la cual std::tuple no puede ser de diseño estándar, como cualquier clase con miembros y clases base con miembros, es que el estándar permite la optimización de espacio cuando deriva incluso clases base no vacías. Por ejemplo:

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

Salidas:

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

Lo anterior muestra que el estándar permite para la clase de salida de relleno a utilizar para los miembros de la clase derivadas. La clase XY tiene dos miembros adicionales uint16_t, pero su tamaño es igual al tamaño de la clase base X.

En otras palabras, el diseño de la clase XY es el mismo que el de otra clase que no tiene clases base y todos los miembros de XY ordenados por dirección, p. struct XY2 { uint64_t a; uint32_t b; uint16_t c; uint16_t d; };.

Lo que lo hace un diseño no estándar es que el tamaño de una clase derivada no es una función de los tamaños de las clases base y los miembros derivados de la clase.

Tenga en cuenta que el tamaño de struct/class es un múltiplo de la alineación de uno de sus miembros con el mayor requisito de alineación. De modo que una matriz de objetos se alinea adecuadamente para dicho miembro. Para tipos incorporados normalmente sizeof(T) == alignof(T). Por lo tanto, sizeof(X) es un múltiplo de sizeof(uint64_t).

no estoy seguro de si la norma requiere un tratamiento especial para struct, pero con g++-5.1.1 si class se sustituye con struct el código anterior produce salida diferente:

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

En otras palabras, la optimización del espacio de relleno de arrastre es no se usa cuando está involucrado struct (no se realizaron pruebas para las condiciones exactas).

Cuestiones relacionadas