Indexación de paquetes (desde C++26)
Accede al elemento de un paquete en un subíndice especificado.
Sintaxis
expresión-id ...[ expression ]
|
(1) | ||||||||
typedef-name ...[ expression ]
|
(2) | ||||||||
| typedef-name | - | Un identificador o un simple-template-id que denomina un paquete. |
| expresión-id | - | Una expresión id que denomina un paquete. |
| expression | - | Una expresión constante convertida I de tipo std::size_t designada como un índice donde I se encuentra dentro del rango [0, sizeof...(P)) para algún paquete P en la indexación de paquete.
|
Explicación
La indexación de paquetes es una expansión de paquete del paquete no expandido seguida de puntos suspensivos e índice dentro del subíndice. Hay dos tipos de indexación de paquetes: expresión de indexación de paquete y especificador de indexación de paquete.
Sea P un paquete no vacío que contiene P0, P1, ..., Pn-1 y I un índice válido, la instanciación de la expansión P...[I] produce el elemento de paquete PI de P.
No se permite indexar un paquete con un índice de expresión no constante I.
int runtime_idx();
void bar(auto... args)
{
auto a = args...[0];
const int n = 1;
auto b = args...[n];
int m = 2;
auto c = args...[m]; // ERROR: 'm' no es una expresión no constante
auto d = args...[runtime_idx()]; // ERROR: 'runtime_idx()' no es una expresión no constante
}
No es posible indexar un paquete de parámetros de plantilla de plantilla.
template <template <typename...> typename... Temps>
using A = Temps...[0]<>; // ERROR: 'Temps' es un paquete de parámetros de plantilla de plantilla
template <template <typename...> typename... Temps>
using B = Temps<>...[0]; // ERROR: 'Temps<>' no denota un nombre de paquete
// aunque es un simple-template-id
Expresión de indexación de paquete
expresión-id ...[ expression ]
|
|||||||||
La expresión de indexación de paquete denota la expresión-id, la expresión del elemento de paquete PI. La expresión-id se introducirá mediante la declaración de:
- Paquete de parámetros de plantilla de no tipo,
- Paquete de parámetros de función,
- Paquete de captura de inicialización de lambda, o
- Paquete de vínculo estructurado.
template <std::size_t I, typename... Ts>
constexpr auto element_at(Ts... args)
{
// 'args' introducido en la declaración del paquete de parámetros de función
return args...[I];
}
static_assert(element_at<0>(3, 5, 9) == 3);
static_assert(element_at<2>(3, 5, 9) == 9);
static_assert(element_at<3>(3, 5, 9) == 4); // ERROR: fuera de límites
static_assert(element_at<0>() == 1); // ERROR: fuera de límites, paquete vacío
template <std::size_t I, typename Tup>
constexpr auto structured_binding_element_at(Tup tup)
{
auto [...elems] = tup;
// 'elems' introducido en la declaración de paquete de vínculo estructurado
return elems...[I];
}
struct A { bool a; int b; };
static_assert(structured_binding_element_at<0>(A {true, 4}) == true);
static_assert(structured_binding_element_at<1>(A {true, 4}) == 4);
// 'Vals' introducido en la declaración de paquete de parámetros de plantilla de no tipo
template <std::size_t I, std::size_t... Vals>
constexpr std::size_t double_at = Vals...[I] * 2; // OK
template <std::size_t I, typename... Args>
constexpr auto foo(Args... args)
{
return [...members = args](Args...[I] op)
{
// 'members' introducido en el paquete de captura inicial de lambda
return members...[I] + op;
};
}
static_assert(foo<0>(4, "Hello", true)(5) == 9);
static_assert(foo<1>(3, std::string("C++"))("26") == "C++26");
No se permite la indexación de paquetes de expresiones complejas distintas de expresión-id.
template <std::size_t I, auto... Vals>
constexpr auto identity_at = (Vals)...[I]; // ERROR
// usar 'Vals...[I]' en su lugar
template <std::size_t I, std::size_t... Vals>
constexpr std::size_t triple_at = (Vals * 3)...[I]; // ERROR
// usar 'Vals...[I] * 3' en su lugar
template <std::size_t I, typename... Args>
constexpr decltype(auto) get(Args&&... args) noexcept
{
return std::forward<Args>(args)...[I]; // ERROR
// usar 'std::forward<Args...[I]>(args...[I])' en su lugar
}
Aplicar decltype a una expresión de indexación de paquetes es lo mismo que aplicar decltype a expresión-id.
void f()
{
[](auto... args)
{
using T0 = decltype(args...[0]); // 'T0' es 'double'
using T1 = decltype((args...[0])); // 'T1' es 'double&'
}(3.14);
}
Especificador de indexación de paquete
typedef-name ...[ expression ]
|
|||||||||
El especificador de indexación de paquete denota el especificador de tipo calculado, el tipo de elemento del paquete PI. El typedef-name se debe introducir mediante la declaración de paquete de parámetros de plantilla de tipo.
template <typename... Ts>
using last_type_t = Ts...[sizeof...(Ts) - 1];
static_assert(std::is_same_v<last_type_t<>, int>); // ERROR: fuera de límites
static_assert(std::is_same_v<last_type_t<int>, int>);
static_assert(std::is_same_v<last_type_t<bool, char>, char>);
static_assert(std::is_same_v<last_type_t<float, int, bool*>, bool*>);
El especificador de indexación de paquete puede aparecer como:
- un especificador de tipo simple,
- un especificador de clase base,
- un especificador de nombre anidado, o
- el tipo de una llamada explícita al destructor.
El especificador de indexación de paquete se puede utilizar en la lista de parámetros de función o constructor para establecer contextos no deducidos en la deducción de argumentos de plantilla.
template <typename...>
struct type_seq {};
template <typename... Ts>
auto f(Ts...[0] arg, type_seq<Ts...>)
{
return arg;
}
// OK: "Hello" se convierte implícitamente a 'std::string_view'
std::same_as<std::string_view> auto a = f("Hello", type_seq<std::string_view>{});
// ERROR: "Ok" no es convertible a 'int'
std::same_as<int> auto b = f("Ok", type_seq<int, const char*>{});
Notas
Antes de C++26, Ts...[N] era una sintaxis válida para declarar paquetes de parámetros de función de arrays sin nombre de tamaño N, donde los tipos de parámetros se ajustaban a punteros. Desde C++26, Ts...[1] se interpreta como un especificador de indexación de paquetes que cambiaría el comportamiento siguiente a #2. Para conservar el primer comportamiento, el paquete de parámetros de función debe tener nombre o ajustarse manualmente a un paquete de tipos puntero.
template <typename... Ts>
void f(Ts... [1]);
template <typename... Ts>
void g(Ts... args[1]);
template <typename... Ts>
void h(Ts*...); // más claro pero más permisivo: Ts... puede contener cv, void o tipos de función
void foo()
{
f<char, bool>(nullptr, nullptr);
// comportamiento #1 (antes de C++26):
// invoca void 'f<char, bool>(char*, bool*)' (también conocido como 'f<char, bool>(char[1], bool[1])')
// comportamiento #2 (desde C++26):
// ERROR: supuestamente se invoca 'void f<char, bool>(bool)'
// pero se le proporcionan 2 argumentos en lugar de 1
g<char, bool>(nullptr, nullptr);
// llama a 'g<char, bool>(char*, bool*)' (aka 'g<char, bool>(char[1], bool[1])')
h<char, bool>(nullptr, nullptr);
// llama a 'h<char, bool>(char*, bool*)'
}
| Macro de prueba de característica | Valor | Estándar | Comentario |
|---|---|---|---|
__cpp_pack_indexing |
202311L |
(C++26) | Indexación de paquetes |
Ejemplo
#include <tuple>
template <std::size_t... Indices, typename Decomposable>
constexpr auto splice(Decomposable d)
{
auto [...elems] = d;
return std::make_tuple(elems...[Indices]...);
}
struct Point
{
int x;
int y;
int z;
};
int main()
{
constexpr Point p { .x = 1, .y = 4, .z = 3 };
static_assert(splice<2, 1, 0>(p) == std::make_tuple(3, 4, 1));
static_assert(splice<1, 1, 0, 0>(p) == std::make_tuple(4, 4, 1, 1));
}