Problem
Implementing product and sum types such as std::tuple
and std::variant
currently rely on lots of template metaprogramming. For example, libc++ roughly implements std::tuple
as follows:
template<size_t I, class T>
struct __tuple_leaf {
static constexpr size_t value = I;
T t;
};
template<class... Ts>
struct tuple : __tuple_leaf<sizeof(Ts), Ts>...
{};
It’s substantially more complex (and tuple
doesn’t directly inherint from __tuple_leaf
), but the idea is that std::tuple
picks up Ts...
without needing to recursively instantiate anything. This is an improvement over recursively inheriting std::tuple
. After turning libc++’s std::tuple
into an aggregate and removing anything else that might be unrelated to a comparison with a hand-written struct that declares all of its elements, it seems that the libc++ implementation is 2.88x the size of something hand-written in debug mode. It’s interestingly smaller when debug symbols are disabled and optimisations are enabled, but this could be an artifact of me not doing a good job of preventing the optimiser from pruning unused stuff (@dblaikie has some ideas on why this might be happening).
This doesn’t take into consideration things like std::get
, std::tuple_cat
, std::visit
, etc., all of which will create lots more in the way of program size.
Proposed solution
It would be good for there to be a way to type out
template<class... Ts>
struct tuple {
Ts... ts;
};
which would be structurally equivalent to
template<class T0, class T1, /* ... */, class Tn>
struct tuple {
T0 t0;
T1 t1;
// ...
Tn tn
};
Element access
Member element access happens as described in P1858 and P2662. That is, we use the subscript operator (ts...[0]
to access t0
), and so on. To access the n th element of ts
, there must be n + 1 elements in the parameter pack; otherwise it’s a compile-time error.
Construction
When constructing a data member pack, it would look similar to how we inherit from a parameter pack.
template<class... Ts>
class tuple {
public:
tuple(Ts&... ts)
: ts_(ts)...
{}
template<class... Us>
requires (sizeof...(Us) == sizeof...(Ts)) and
(std::constructible_from<Ts, Us> and ...)
tuple(Us&&... us)
: ts_(std::forward<Us>(us))...
{}
private:
Ts... ts_;
};
Benefits
- improved program sizes at both compile-time and runtime;
- improved compilation speeds since we don’t need to instantiate so many types to facilitate
std::tuple
andstd::variant
(and third-party equivalents); - more readable diagnostics, since names will be smaller;
- potentially improved debugger performance for very large programs;
- substantially simplified implementations such as
template<size_t N, class... Ts>
T& get(tuple<Ts...>& t) noexcept
{
return t.ts...[N];
}
Costs
- either needs to be a Clang extension or standardised in C++26
- standard libraries might not be able to migrate without breaking ABI
- @zygoloid notes that this introduces the possibility of encountering parameter packs outside of templates, which Clang doesn’t have any way of modelling at the moment. Unless it would require a complete redesign of the feature, I think we could implement this bit independently of the template section, although it absolutely needs to be implemented. I don’t fully understand the implementation concerns on this point, and I think we need to discuss this in greater detail.
Extension or standardisation?
I intend to write a proposal to WG21 to standardise this feature (libc++ cannot be expected to maintain two versions of std::tuple
and std::variant
for two different compilers). However, this feature is already on the committee’s radar thanks to P1858, and I consider it critically important for there to be an implementation before features are standardised; preferably with deployment experience so that we can see the benefits and drawbacks of the feature in practice, as opposed to trying to work them out from reasoned motivation. As such, the committee proposal for this feature will happen in parallel to its implementation.
The first revision of the proposal will be largely similar to this proposal, likely with changes applied as feedback is received.