添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
没有腹肌的苹果  ·  Setting Scroll ...·  1 月前    · 
着急的乌冬面  ·  Cannot cast ...·  4 月前    · 
光明磊落的铁链  ·  Sending a number over ...·  8 月前    · 
读研的灭火器  ·  String (Java SE 11 & ...·  1 年前    · 
Date: 2017-10-12
Project: Programming Language C++, Library Evolution Working Group
Reply-to: Agustín Bergé [email protected]

A friendlier tuple get

1. Introduction

This paper proposes changing std::get overloads to behave gracefully in the presence of user defined get overloads.

2. Motivation

Consider the following example, presented on Cpplang at Slack:

template < typename ... Ts>
struct WeirdTuple : std::tuple <Ts...> {
using std::tuple <Ts...>::tuple;
template < std::size_t I, typename ... Ts>
auto get(WeirdTuple<Ts...>& t) { return I + 10; }
int main() {
WeirdTuple< int > wt(1);
get<0>(wt); // changing this 0 to anything else will break
// theoretically, that should still work -
// we're obviously using the WeirdTuple overload of get<>(),
// but the compiler fails when trying to compile every possible overload!
get<1>(wt); // error: static assertion failed: tuple index is in range
// in instantiation of 'std::tuple_element<1, std::tuple<int>>'
// required by substitution of
//   constexpr std::tuple_element_t<I, std::tuple<Ts...>>&
//   std::get(std::tuple<Ts...>&)
//   [with I = 1; Ts = {int}]

The confusion arises from a disagreement between the programmer and the implementation on the "obviousness" of the intented target. A sufficiently advanced implementation might realize that no std::get overload would possibly be a better match than the WeirdTuple overload and thus skip substitution altogether, but it is not required to do so. During that substitution process the std::get overloads render the program ill-formed, effectively poisoning the overload set.

3. Discussion

3.1 SFINAE-friendly

A traditional SFINAE-friendly implementation will get out of the user's way when used with an out of bounds index. The main disadvantage of this approach is that by not participating in overload resolution, it opens the door for user defined overloads even when called on a standard library tuple-like type; that is, given t of type std::tuple<UDT> , get<1>(t) might silently fall back to a get overload in an associated namespace of UDT . This regresses key functionality in the current std::get design, which mandates a diagnostic for out of bound access.

Possible implementation:

template < std::size_t I, typename ...Ts,
typename Enable = std::enable_if_t <I < sizeof ...(Ts)>>
std::tuple_element_t <I, std::tuple <Ts...>>&
get( std::tuple <Ts...>& t) {
return /*...*/ ;
std::tuple < int > t;
std::get <1>(t); // error: no matching function for call to 'get<1>(std::tuple<int>&)'
// note: candidate template ignored:
//   std::get(array<Ts...>&)
//   could not match 'array' against 'tuple'
// note: candidate template ignored:
//   std::get(pair<Ts...>&)
//   could not match 'pair' against 'tuple'
// note: candidate template ignored:
//   std::get(variant<Ts...>&)
//   could not match 'variant' against 'tuple'
// note: candidate template ignored:
//   std::get(std::tuple<Ts...>&)
//   [with I = 1; Ts = {int}]
//   requirement 'I < sizeof...(Ts)' was not satisfied

3.2 Conditionally Deleted

A conditionally deleted implementation prevents the unintended fall back behavior of the traditional SFINAE-friendly approach, while still remaining SFINAE-friendly. As a bonus, diagnostics on out of bound access tend to be concise.

Possible implementation:

template < std::size_t I, typename ...Ts,
typename Enable = std::enable_if_t <I < sizeof ...(Ts)>>
std::tuple_element_t <I, std::tuple <Ts...>>&
get( std::tuple <Ts...>& t) {
return /*...*/ ;
template < std::size_t I, typename ...Ts>
std::enable_if_t < sizeof ...(Ts) <= I>
get( std::tuple <Ts...>& t) = delete ;
std::tuple < int > t;
std::get <1>(t); // error: call to deleted function 'get'
// note: declared here
//   std::get(std::tuple<Ts...>&) = delete;
//   [with I = 1; Ts = {int}]

3.3 Deduced Return Type

An implementation that uses deduced return types can defer the required diagnostic until the definition is instantiated. The main disadvantage is that such definition may need to be instantiated earlier/more often than an explicitly typed alternative, and that the result is SFINAE-unfriendly in those contexts. On the other side, diagnostics on out of bound access tend to be concise and could include a custom tailored message.

Possible implementation:

template < std::size_t I, typename ...Ts>
decltype ( auto ) get( std::tuple <Ts...>& t) {
static_assert (I < sizeof ...(Ts), "tuple index is in range" );
return /*...*/ ;
std::tuple < int > t;
std::get <1>(t); // error: static assertion failed: tuple index is in range
// in instantiation of
//   std::get(std::tuple<Ts...>&)
//   [with I = 1; Ts = {int}]

-Y- Requires : The type T occurs exactly once in Types... . Otherwise, the program is ill-formed.

-?- Remarks : This function shall not participate in overload resolution unless the type T occurs exactly once in Types... .

4.2 Option B, Conditionally Deleted

-Y- Requires : The type T occurs exactly once in Types... . Otherwise, the program is ill-formed.

-?- Remarks : This function shall be defined as deleted unless the type T occurs exactly once in Types... .

4.3 Option C, Deduced Return Type

template < size_t I, class ... Types>
constexpr tuple_element_t<I, tuple<Types...>>& decltype ( auto )
get(tuple<Types...>& t) noexcept ;

-Y- Requires : The type T occurs exactly once in Types... . Otherwise, the program is ill-formed.

5. References