Topic: Making tuple concept-friendly


Author: Geoffrey Romer <gromer@google.com>
Date: Tue, 21 Jan 2014 15:28:23 -0800
Raw View
--001a11c13b524e044904f0835d90
Content-Type: text/plain; charset=UTF-8

The standard appears to envision the possibility of a Tuple concept: for
example, [array.tuple] and [pair.astuple] provide specializations and
overloads to give array and pair a "tuple interface" or "tuple-like" API,
and a note at [tuple.creation]/p13 refers to array and pair as examples of
types that "support the tuple-like protocol".

However, the Tuple concept/protocol is never explicitly defined, and the
tuple API is actually surprisingly hostile to generic programming. For
example, I recently attempted to write some code that could work
generically with std::tuple or std::tr1::tuple, only to discover that this
is basically not possible, despite the fact that their APIs are nearly
identical.

The first problem is that the tuple API relies heavily on non-member
metafunctions such as tuple_size and tuple_element. Since they are not
members, they can't be found using dependent name lookup (IOW, C++ doesn't
have ADL for metafunctions), so you have to explicitly say
"std::tuple_size<T>" or "std::tr1::tuple_size<T>". One possible fix I've
heard proposed is to establish the convention that all tuple-like types
must specialize the std:: metafunctions, and then generic code can just use
those. Of course, that doesn't help for std::tr1::tuple, since such
specializations don't exist, and cannot be provided by the user.

The second problem is that get(), the core of the tuple API, requires an
explicit template parameter. Consequently, it falls afoul of
[basic.lookup.classref]/p1: an expression like "get<0>(t)" is not eligible
for argument-dependent lookup unless the name "get" first resolves to a
template via a non-argument-dependent lookup.This problem is not
correctable by saying that generic code should just use std::get, because
std::get is a template function, and hence cannot be partially specialized
by other tuple types. It can be overloaded, but of course the standard does
not permit user code to add overloads to the ::std namespace. There is a
workaround, namely defining a dummy "get" template in the namespace of the
caller to resolve the parse ambiguity, but this is a far from ideal
solution.

The most obvious solution here would be to extend std::tuple (and pair and
array?) so that all operations can be expressed in terms of class or
instance members of the tuple type:

static_assert(std::tuple<int, string>::tuple_size == 2, "");
static_assert(std::is_same<std::tuple<int, string>::tuple_element<0>,
int>::value, "");
std::tuple<int, string> t{42, ""};
assert(t.get<0>() == 42);

This would provide the basis for an unambiguous Tuple concept, whether
explicitly specified by the standard or not. Personally, I find such an API
much more natural than one based on namespace-scoped [meta]functions, as
well as more amenable to generic programming. Is there any reason this
wouldn't work? The major drawback I see is that it would clutter these APIs
(especially std::pair and std::array, which also have to provide a
non-tuple API), giving them two distinct ways of saying the same thing.

A variant of the above would be to provide get<>() as a member function,
but leave the metafunctions as they are, on the theory that they can be
handled by partial specialization.

Alternatively, it might be possible to modify the language itself to be
more amenable to generic programming with these sorts of interfaces. For
example, we could alter the parsing rules somehow to resolve the get<>()
issue, and make some provision for ADL in metafunctions (perhaps explicitly
invoked by the caller, if it's not too burdensome?). This could benefit
other APIs besides tuple, and might enable use cases like my original
attempt to generically support both std::tuple and std::tr1::tuple; I
gather the tr1::tuple API is effectively fossilized at this point, so that
can't be fixed at the library level. However, this seems like a much more
complex undertaking, so probably not worthwhile unless the payoff is more
substantial.

What do you think?

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

--001a11c13b524e044904f0835d90
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div>The standard appears to envision the possibility of a=
 Tuple concept: for example, [array.tuple] and [pair.astuple] provide speci=
alizations and overloads to give array and pair a &quot;tuple interface&quo=
t; or &quot;tuple-like&quot; API, and a note at [tuple.creation]/p13 refers=
 to array and pair as examples of types that &quot;support the tuple-like p=
rotocol&quot;.=C2=A0</div>

<div><br></div><div>However, the Tuple concept/protocol is never explicitly=
 defined, and the tuple API is actually surprisingly hostile to generic pro=
gramming. For example, I recently attempted to write some code that could w=
ork generically with std::tuple or std::tr1::tuple, only to discover that t=
his is basically not possible, despite the fact that their APIs are nearly =
identical.=C2=A0</div>

<div><br></div><div>The first problem is that the tuple API relies heavily =
on non-member metafunctions such as tuple_size and tuple_element. Since the=
y are not members, they can&#39;t be found using dependent name lookup (IOW=
, C++ doesn&#39;t have ADL for metafunctions), so you have to explicitly sa=
y &quot;std::tuple_size&lt;T&gt;&quot; or &quot;std::tr1::tuple_size&lt;T&g=
t;&quot;. One possible fix I&#39;ve heard proposed is to establish the conv=
ention that all tuple-like types must specialize the std:: metafunctions, a=
nd then generic code can just use those. Of course, that doesn&#39;t help f=
or std::tr1::tuple, since such specializations don&#39;t exist, and cannot =
be provided by the user.</div>

<div><br></div><div>The second problem is that get(), the core of the tuple=
 API, requires an explicit template parameter. Consequently, it falls afoul=
 of [basic.lookup.classref]/p1: an expression like &quot;get&lt;0&gt;(t)&qu=
ot; is not eligible for argument-dependent lookup unless the name &quot;get=
&quot; first resolves to a template via a non-argument-dependent lookup.Thi=
s problem is not correctable by saying that generic code should just use st=
d::get, because std::get is a template function, and hence cannot be partia=
lly specialized by other tuple types. It can be overloaded, but of course t=
he standard does not permit user code to add overloads to the ::std namespa=
ce. There is a workaround, namely defining a dummy &quot;get&quot; template=
 in the namespace of the caller to resolve the parse ambiguity, but this is=
 a far from ideal solution.</div>
<div><br></div><div>The most obvious solution here would be to extend std::=
tuple (and pair and array?) so that all operations can be expressed in term=
s of class or instance members of the tuple type:</div><div><br></div><div>
static_assert(std::tuple&lt;int, string&gt;::tuple_size =3D=3D 2, &quot;&qu=
ot;);</div><div>static_assert(std::is_same&lt;std::tuple&lt;int, string&gt;=
::tuple_element&lt;0&gt;, int&gt;::value, &quot;&quot;);</div><div>std::tup=
le&lt;int, string&gt; t{42, &quot;&quot;};<br>
</div><div>assert(t.get&lt;0&gt;() =3D=3D 42);</div><div><br></div><div>Thi=
s would provide the basis for an unambiguous Tuple concept, whether explici=
tly specified by the standard or not. Personally, I find such an API much m=
ore natural than one based on namespace-scoped [meta]functions, as well as =
more amenable to generic programming. Is there any reason this wouldn&#39;t=
 work? The major drawback I see is that it would clutter these APIs (especi=
ally std::pair and std::array, which also have to provide a non-tuple API),=
 giving them two distinct ways of saying the same thing.</div>
<div><br></div><div>A variant of the above would be to provide get&lt;&gt;(=
) as a member function, but leave the metafunctions as they are, on the the=
ory that they can be handled by partial specialization.</div><div><br></div=
>
<div>Alternatively, it might be possible to modify the language itself to b=
e more amenable to generic programming with these sorts of interfaces. For =
example, we could alter the parsing rules somehow to resolve the get&lt;&gt=
;() issue, and make some provision for ADL in metafunctions (perhaps explic=
itly invoked by the caller, if it&#39;s not too burdensome?). This could be=
nefit other APIs besides tuple, and might enable use cases like my original=
 attempt to generically support both std::tuple and std::tr1::tuple; I gath=
er the tr1::tuple API is effectively fossilized at this point, so that can&=
#39;t be fixed at the library level. However, this seems like a much more c=
omplex undertaking, so probably not worthwhile unless the payoff is more su=
bstantial.</div>
<div><br></div><div>What do you think?</div>
</div>

<p></p>

-- <br />
&nbsp;<br />
--- <br />
You received this message because you are subscribed to the Google Groups &=
quot;ISO C++ Standard - Future Proposals&quot; group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to std-proposals+unsubscribe@isocpp.org.<br />
To post to this group, send email to std-proposals@isocpp.org.<br />
Visit this group at <a href=3D"http://groups.google.com/a/isocpp.org/group/=
std-proposals/">http://groups.google.com/a/isocpp.org/group/std-proposals/<=
/a>.<br />

--001a11c13b524e044904f0835d90--

.