type_traits: fix empty pack indexing#387
Conversation
Compiling with C++26 fails on empty packs with this message:
include/stdx/type_traits.hpp:302:7: error: cannot index an empty pack
302 | using nth_t =
| ^~~~~
The fix is to prioritize __type_pack_element over C++26 pack indexing in
type_traits.hpp, since the builtin evaluates lazily (only when a member is
accessed) while pack indexing is checked eagerly when the class is
instantiated with an empty pack.
Signed-off-by: Benedek Kupper <kupper.benedek@gmail.com>
|
Thanks! Can you give an example of code that would fail currently, so that we can have a test? |
|
The context is that I'm using CIB's // Minimal reproduction of the C++26 pack indexing bug in stdx::nth_t.
//
// PROBLEM
// -------
// With C++26 pack indexing (`Ts...[N]`), the compiler eagerly validates
// the expression when the surrounding class template is instantiated, even
// if the alias member is never actually used. When the pack is empty the
// expression `{}...[I]` is always ill-formed, so instantiating e.g.
// `message<"foo">` (a message with no fields) produces:
//
// error: cannot index an empty pack
//
// FIX
// ---
// Prefer `__type_pack_element<N, Ts...>` (GCC/Clang builtin) over the
// C++26 pack-indexing syntax. The builtin is evaluated lazily: it is only
// checked when the alias is actually instantiated with a concrete index,
// so an empty pack in the class body does not trigger an error.
//
// Reproduces with: g++ -std=c++26 (GCC ≥ 15, __cpp_pack_indexing defined)
// Fixed with: prefer __type_pack_element over Ts...[N]
#include <cstddef>
#include <type_traits>
// ── nth_t with C++26 pack indexing (BROKEN for empty packs) ──────────────────
template <unsigned int N, typename... Ts>
using nth_t_broken = Ts...[N]; // C++26 pack indexing
// ── nth_t preferring the builtin (FIXED) ─────────────────────────────────────
template <unsigned int N, typename... Ts>
using nth_t_fixed =
#if __has_builtin(__type_pack_element)
__type_pack_element<N, Ts...>;
#else
Ts...[N]; // fallback if builtin unavailable
#endif
// ── Class that exposes nth_field_t (mirrors msg::detail::message) ─────────────
template <typename... Fields>
struct message_broken {
// Eagerly checked when Fields={}: error: cannot index an empty pack
template <std::size_t I> using nth_field_t = nth_t_broken<(unsigned int)I, Fields...>;
};
template <typename... Fields>
struct message_fixed {
// Lazily checked; no error when Fields={}
template <std::size_t I> using nth_field_t = nth_t_fixed<(unsigned int)I, Fields...>;
};
// ── Instantiate with no fields ────────────────────────────────────────────────
// Uncomment the broken line to see the error:
// message_broken<> broken_instance; // error: cannot index an empty pack
message_fixed<> fixed_instance; // OK
// ── Instantiate with fields – nth_field_t must still work ─────────────────────
struct FieldA {};
struct FieldB {};
static_assert(std::is_same_v<message_fixed<FieldA, FieldB>::nth_field_t<0>, FieldA>);
static_assert(std::is_same_v<message_fixed<FieldA, FieldB>::nth_field_t<1>, FieldB>);
int main() {} |
|
I think this is just a GCC bug, right? It's not supposed to instantiate the alias member template when the class template is instantiated. Clang treats this correctly. |
Correct, the bug is present up to the latest GCC version, while clang compilation passes. |
Compiling with C++26 fails on empty packs with this message:
include/stdx/type_traits.hpp:302:7: error: cannot index an empty pack
302 | using nth_t =
| ^~~~~
The fix is to prioritize __type_pack_element over C++26 pack indexing in
type_traits.hpp, since the builtin evaluates lazily (only when a member is
accessed) while pack indexing is checked eagerly when the class is
instantiated with an empty pack.