tuple cpp c++ c++11 language-lawyer stdtuple

cpp - std:: tuple c++



Confundido por la descripción del constructor por defecto de std:: tuple en el estándar ISO C++ (3)

El estándar dice que std::tuple tiene las siguientes funciones de miembro

constexpr tuple(); explicit tuple(const Types&...);

¿Alguien puede explicar qué se supone que sucederá con std::tuple<> ?


A primera vista, la ambigüedad solo importaría en el punto donde se llama, y ​​luego tendrá una resolución de sobrecarga normal.


Creo que esto es un error menor en la norma. Claramente, cuando el paquete de parámetros de Types está vacío, las dos llamadas del constructor son equivalentes y no se pueden sobrecargar (ver C ++ 11 sección 13). (Además, tenga en cuenta que el constructor que usa Types tampoco es una plantilla miembro, si lo fue, sería una sobrecarga legal).

En otras palabras, este código no compilará:

template <typename... Types> struct Test { constexpr Test() {} explicit Test(Types const&...) { /* etc. */ } }; int main() { Test<> a; Test<int> b; }

por ejemplo, un g ++ v4.8 snapshot produce:

tt.cxx: In instantiation of ‘struct Test<>’: tt.cxx:10:10: required from here tt.cxx:5:12: error: ‘Test<Types>::Test(const Types& ...) [with Types = {}]’ cannot be overloaded explicit Test(Types const&...) { /* etc. */ } ^ tt.cxx:4:13: error: with ‘constexpr Test<Types>::Test() [with Types = {}]’ constexpr Test() {} ^

Esto se puede solucionar mediante el uso de especialización parcial:

template <typename... Types> struct Test { constexpr Test() {} // default construct all elements explicit Test(Types const&...) { /* etc. */ } // and all other member definitions }; template <> struct Test<> { constexpr Test() {} // and any other member definitions that make sense with no types }; int main() { Test<> a; Test<int> b; }

que compilará correctamente.

Parece que el estándar quería un constructor predeterminado constexpr para que std::tuple<> var; podría escribirse en lugar de escribir std::tuple<> var(); o std::tuple<> var{}; Por el uso de explicit con el otro constructor. Desafortunadamente, su definición de std::tuple no funciona para tuplas de tamaño cero. La norma sí lo permite en la sección 20.4.2.7 (operadores relacionales), sin embargo, "Para dos tuplas de longitud cero, [...]". Ups! :-)


Supongo que la definición dada en el estándar se supone que es pseudocódigo. Ese es el caso con muchas de las definiciones en la norma; Contiene varios requisitos que se dan verbalmente, pero solo son satisfactorios con trucos como enable_if . Este parece ser un ejemplo en el que la notación de pseudocódigo similar a C ++ en realidad puede llevar a C ++ ilegal cuando se trata de crear una instancia de una tupla vacía (o podría ser una omisión).

Tanto stdlibc ++ como libc ++ tienen una especialización explícita para la tupla del elemento cero. Por ejemplo, en stdlibc ++:

// Explicit specialization, zero-element tuple. template<> class tuple<> { public: void swap(tuple&) noexcept { /* no-op */ } };

con un constructor por defecto inequívoco implícitamente definido.

Libc ++ no declara explícitamente el constructor predeterminado sin parámetros. Presumiblemente, el constructor de plantilla se elige como constructor predeterminado para tuplas no vacías.

Curiosamente, las dos bibliotecas no están de acuerdo sobre qué miembros tiene la tupla vacía. Por ejemplo, lo siguiente compila con libc ++, pero no con libstdc ++:

#include <tuple> #include <memory> int main() { std::tuple<> t(std::allocator_arg, std::allocator<int>()); }