std::tuple
IndexingDocument #: | P2726R0 |
Date: | 2022-11-18 |
Project: | Programming Language C++ |
Audience: |
LEWG-I LEWG |
Reply-to: |
Zach Laine <whatwasthataddress@gmail.com> |
std::tuple<int>
can be
improvedA common complaint that I hear about
std::tuple
from C++ users is
that getting the Nth value out of it is painfully verbose. Fortunately,
if P2725
std::integral_constant
literals is adopted, we can do better.
Before
|
After
|
---|---|
|
|
The expressions
std::get<0>(t)
and
t[0ic]
are semantically
identical, but syntactically the former is very noisy and the latter is
not. All either operation does is get a reference to the Nth element of
the tuple. t[0ic]
expresses that
concisely.
There are multiple options here, as indicated in the initial example above. Please note that both the options rely on the existence of P2725.
Add an operator[]
to
std::tuple
.
This design does not come from me. It is the way that Boost.Hana’s tuples work. It’s been around a long time, and people really seem to like it.
In more indexing-heavy code, Boost.Hana-style concision really helps.
Say you have a context object
ctx
that contains a large number
of tuples used to capture configury and transient state, and multple
accessors _foo()
that return
references to tuples in ctx
:
Before
|
After
|
---|---|
|
|
Also, in any situation where tuples are nested, the use of the index operator makes things much clearer:
Before
|
After
|
---|---|
|
|
There is an alternative to adding a new operation to
std::tuple
– we could just add
an overload of std::get()
that
takes a std::integral_constant
as a function parameter. I don’t think the results are nearly as
nice:
Before
|
After
|
---|---|
|
|
This effectively replaces
<>
with
,
and
ic
, which is slightly more
typing. It also leaves the noisiest part,
std::get
, still in play.
This option does have the advantage that it could be used to address
non- std::tuple
uses of
std::get()
as well (though that
is not proposed here). If you happen already to have a
std::integral_constant
ic
lying about, you can use it
directly as a function call arg. It saves you from having to type
ic.value
, I guess.
This option helps slightly in a nested-tuple situation, in that the indices no longer appear in reverse order:
Before
|
After
|
---|---|
|
|
As stated earlier, this has been implemented in Boost.Hana’s tuple
for years. The implementation is straightforward, especially since all
we need to do is add a new
operator[]
that just calls
std::get()
, (Option 1) or add a
new set of overloads of
std::get()
each of which calls
one of the old ones (Option 2).
In 22.4.4
[tuple.tuple], add
this new member function to
tuple
:
template<class Self, class IndexType, IndexType I> constexpr decltype(auto) operator[](this Self && self, integral_constant<IndexType, I>) { return std::get<I>(std::forward<Self>(self); }
In 22.4.2 [tuple.syn], append these function templates to the end of the [tuple.elem] section:
template<class IndexType, IndexType I, class... Types>
constexpr decltype(auto) get(tuple<Types...>& t) noexcept { return std::get<I>(t); }
template<class IndexType, IndexType I, class... Types>
constexpr decltype(auto) get(tuple<Types...>&& t) noexcept { return std::get<I>(std::move(t)); }
template<class IndexType, IndexType I, class... Types>
constexpr decltype(auto) get(const tuple<Types...>& t) noexcept { return std::get<I>(t); }
template<class IndexType, IndexType I, class... Types> constexpr decltype(auto) get(const tuple<Types...>&& t) noexcept { return std::get<I>(std::move(t)); }