Topic: Defect Report: tuple::tail() should be in the public interface
Author: Douglas Gregor <doug.gregor@gmail.com>
Date: Wed, 23 May 2007 11:56:55 CST Raw View
On May 23, 1:22 am, jgott...@carolina.rr.com (Joe Gottman) wrote:
> My proposed concatenating constructor would require that
> corresponding parameters fulfill the Convertible concept. If you have
> two types U and V such that V has an explicit constructor that takes a
> parameter of type U, is U considered to be Convertible to V?
No, Convertible models implicit conversions, only.
> If not,
> then the example you site will unambiguously call the first constructor,
> because the constructor tuple<int>::tuple(const int &) is defined to be
> explicit. If U is considered to be convertible to V when an explicit
> constructor is used, then I will have to think about the problem a
> little more.
Ah, okay. A good two paragraphs to put into the extension proposal for
the committee, because someone else is bound to bring up the issue.
- Doug
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Douglas Gregor <doug.gregor@gmail.com>
Date: Wed, 23 May 2007 11:57:09 CST Raw View
On May 22, 12:14 pm, cppljev...@suddenlink.net (Larry Evans) wrote:
> On 05/21/2007 12:48 PM, Douglas Gregor wrote:
>
> What about:
>
> enum
> tuple_implementation
> { tuple_list
> , tuple_vec
> , tuple_inherit
> };
> template
> < class Types...
> , tuple_implementation Impl=tuple_list
> >
> struct
> tuple
> ;
>
> The standard could require implementer's to at least provide
> the above elements of tuple_implementation but allow
> additions. The default would be like todays with the
> addition of head and tail functions, tuple_vec would be
> like fusion's (saving space), and the last would be
> Douglas' favorite inherit version.
Ick, no. tuple<> is a simple thing, and should stay that way. Mixing
in policies on the storage mechanism---which is irrelevant to all of
the operations except the proposed tail()---just means making tuple<>
far more complicated for implementers and users alike.
> Of course there's the small problem that the current variadic
> template compiler only allows Types... at the end of the
> template formal parameter list :(
Even with this limitation, you could implement what you describe
above. You would need to tuple_list, tuple_vec, and tuple_inherit
classes instead of enums, though.
- Doug
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: jgottman@carolina.rr.com (Joe Gottman)
Date: Fri, 25 May 2007 16:02:18 GMT Raw View
Joe Gottman wrote:
> Douglas Gregor wrote:
>
>>
>> The ambiguity is in the construction of tuple<tuple<int>,
>> tuple<float>>. With the addition of your concatenating constructor, it
>> has:
>>
>> tuple(tuple<int>, tuple<float>);
>>
>> template<typename... TTypes, typename... UTypes>
>> tuple(tuple<TTypes...> const& x, tuple<UTypes...> const& y);
>>
>> Which constructor gets called for my example above? Is it the
>> constructor you expected? Are there any cases where the "wrong"
>> constructor gets called, meaning that we need to say something about
>> the interaction of these constructors? If there aren't any such cases,
>> why not?
>
> My proposed concatenating constructor would require that corresponding
> parameters fulfill the Convertible concept. If you have two types U and
> V such that V has an explicit constructor that takes a parameter of type
> U, is U considered to be Convertible to V? If not, then the example you
> site will unambiguously call the first constructor, because the
> constructor tuple<int>::tuple(const int &) is defined to be explicit.
> If U is considered to be convertible to V when an explicit constructor
> is used, then I will have to think about the problem a little more.
>
I've just realized that the situation is much worse than I thought.
The problem is with a tuple<boost::any, boost::any>. Almost every
other type is Convertible to boost::any. In particular, all tuples are
Convertible to boost::any, so the following code compiles under the
current rules:
using namespace boost;
tuple<any, any> tup1(make_tuple(1), make_tuple(2.0));
tuple<any, any> tup2(make_tuple(), make_tuple(1, 2.0));
After this code is run, get<0>(tup1).type() == typeid(tuple<int>),
get<1>(tup1).type() == typeid(tuple<double>), get<0>(tup2).type() ==
typeid(tuple<>), and get<1>(tup2).type() == typeid(tuple<int, double>).
This causes ambiguities with my proposed concatenating constructor, no
matter how strictly I restrict the input types.
Joe Gottman
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: jgottman@carolina.rr.com (Joe Gottman)
Date: Sun, 20 May 2007 21:20:08 GMT Raw View
As std::tuple is currently written, it is difficult to impossible to
write functions that take a tuple of indeterminate size as a parameter
and make use of the entire tuple. For example, consider a function that
writes the contents of a tuple to a stream.
template <class... Types>
inline ostream &print_tuple(ostream &os, const tuple<Types...> &tup);
It is impossible to use a for-loop and tuple::get to write this
function, because tuple::get<N> requires N to be a compile-time constant
while a for-loop requires N to be a variable. The programmer would have
to write an implementation function templated on an int and the Types:
template <int N, class...Types>
ostream &print_tuple_imp(ostream &os, const tuple<Types...> &tup);
This is overly burdensome for the average programmer, especially since
the rules of templates require that N be declared first in the template
list, where it cannot mention the parameter pack Types... . If
tuple::tail() were available in the public interface, the implementation
function would not be necessary:
// Default implementation
template <class ... Types>
ostream &print_tuple(ostream &os, const tuple<Types ...> &tup)
{
os << get<0>(tup) << " ";
return print_tuple(os, tup.tail());
}
//overload for tuple<>
inline ostream &print_tuple(ostream &os, const tuple<> &tup)
{
return os; // Nothing to do with an empty tuple;
}
Note that the definition of operator <() on tuples already uses the
tail() function, even though it is careful not to name it as such.
Another simple function on tuples that is difficult to write as tuple is
currently defined is a function that concatenates two tuples
template <class ...Types1, class ... Types2>
inline tuple<Types1..., Types2...>
concatenate(const tuple<Types1...> &tup1, const tuple<Types2...> &tup2);
Thus, if tup1 is a tuple<int, string> constaining {1, "Hello"} and tup2
is a tuple <int, double, char> containing {1, 2.0, '3'}, then
concatenate(tup1, tup2) would be a tuple<int, string, int, double, char>
containing {1, "Hello", 1, 2.0, '3'}. It is very useful and elementary
to concatenate two tuples, but as tuples are written now it would be
very difficult to implement this function, even using an implementation
function templated on int. In order to implement this, it would be
useful to add two templated constructors to tuple. The first is a
constructor that takes a scalar object and a tuple of size one less than
the size of the tuple we are constructing. It would initialize the head
of the tuple with the scalar object and the tail with the tuple. Using
this constructor and constructor forwarding, it is possible to define a
constructor that takes two tuples such that the sum of their sizes
equals the size of the tuple we are constructing.
Finally, even though it is not strictly necessary, it is probably a good
idea to define a head() member function for tuples that returns get<0>,
simply to aid in programmer understanding of the tail() function.
Proposed Resolution:
Change 20.3.1 as follows:
add the following public typedefs and member functions to tuple:
typedef tuple_element<0, tuple> head_type; // iff sizeof...(Types) > 0
typedef tuple<TailTypes...> tail_type; // iff sizeof...(Types) > 0,
where TailTypes... is a parameter pack containing all the types in
Types... except the first.
head_type &head();//iff sizeof...(Types) > 0
const head_type &head() const; //iff sizeof...(Types) > 0
tail_type &tail(); // iff sizeof...(Types) > 0
const tail_type &tail() const; // iff sizeof...(Types) > 0
template <class U, typename... UTypes>
tuple(U &&u, const tuple<UTypes...> &tup); // iff sizeof...(Types) ==
sizeof...(UTypes) + 1
template <class U, typename... UTypes>
tuple(U &&u, tuple<UTypes...> &&tup); // iff sizeof...(Types) ==
sizeof...(UTypes) + 1
template <class UTypes..., class VTypes...>
tuple(const tuple<UTypes> &tup1, const tuple<VTypes> &tup2); // iff
sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
template <class UTypes..., class VTypes...>
tuple(const tuple<UTypes> &tup1, tuple<VTypes> &&tup2); // iff
sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
template <class UTypes..., class VTypes...>
tuple(tuple<UTypes> &&tup1, const tuple<VTypes> &tup2); // iff
sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
template <class UTypes..., class VTypes...>
tuple(tuple<UTypes> &&tup1, tuple<VTypes> &&tup2); // iff
sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
Add the following definitions to 20.3.1.1:
template <class U, typename... UTypes>
tuple(U &&u, tuple<UTypes...> &&tup);
1) Requires: sizeof...(Types) == sizeof...(UTypes) + 1, U is convertible
to head_type, tuple<UTypes> is convertible to tail_type
2) Effects: constructs head() with forward<U>(u) and tail() with tup
template <class UTypes..., class VTypes...>
tuple(const tuple<UTypes> &tup1, const tuple<VTypes> &tup2); // iff
sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
1) Requires: sizeof...(Types) == sizeof...(UTypes) + 1, U is convertible
to head_type, tuple<UTypes> is convertible to tail_type
2) Effects: constructs head() with forward<U>(u) and tail() with move(tup)
template <class UTypes..., class VTypes...>
tuple(const tuple<UTypes> &tup1, tuple<VTypes> &&tup2);
1) Requires: sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes),
The first sizeof...(UTypes) parameters in Types are constructable
from the corresponding elements in UTypes, and the rest are
constructable from the corresponding elements in VTypes
2) Effects: If sizeof...(UTypes) == 0, constructs the tuple from tup2.
Otherwise, constructs head() from tup1.head() and tail() from
tup1.tail() and tup2.
template <class UTypes..., class VTypes...>
tuple(tuple<UTypes> &&tup1, const tuple<VTypes> &tup2);
1) Requires: sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes),
The first sizeof...(UTypes) parameters in Types are constructable
from the corresponding elements in UTypes, and the rest are
constructable from the corresponding elements in VTypes
2) Effects: If sizeof...(UTypes) == 0, constructs the tuple from tup2.
Otherwise, constructs head() from move(tup1.head()) and tail() from
move(tup1.tail()) and tup2.
template <class UTypes..., class VTypes...>
tuple(const tuple<UTypes> &tup1, tuple<VTypes> &&tup2);
1) Requires: sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes),
The first sizeof...(UTypes) parameters in Types are constructable
from the corresponding elements in UTypes, and the rest are
constructable from the corresponding elements in VTypes
2) Effects: If sizeof...(UTypes) == 0, constructs the tuple from
move(tup2). Otherwise, constructs head() from tup1.head() and tail()
from move(tup1.tail() and move(tup2).
template <class UTypes..., class VTypes...>
tuple(tuple<UTypes> &&tup1, tuple<VTypes> &&tup2); // iff
sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
1) Requires: sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes),
The first sizeof...(UTypes) parameters in Types are constructable
from the corresponding elements in UTypes, and the rest are
constructable from the corresponding elements in VTypes
2) Effects: If sizeof...(UTypes) == 0, constructs the tuple from
move(tup2). Otherwise, constructs head() from move(tup1.head()) and
tail() from move(tup1.tail()) and move(tup2).
Add a section for tuple access functions:
head_type &head()
const head_type &head() const
1) Requires: sizeof...(Types) > 0
2) Returns: get<0>(*this)
tail_type &tail()
const tail_type &tail() const;
1) Requires: sizeof...(Types) > 0
2) Returns: A reference to a tuple consisting of all the elements of
*this except the first.
[Note: No elements are copied and no copy constructor is called]
Change the definition of tuple::operator<() in section 20.3.1.4 so that
the returns clause reads:
Returns: The result of a lexicographical comparison between t and u. The
result is defined as: (bool)(get<0>(t)
< get<0>(u)) || (!(bool)(get<0>(u) < get<0>(t)) && u.tail() < v.tail()).
For any two zero-length tuples e and f, e < f returns false.
Joe Gottman
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: cbarron3@ix.netcom.com (Carl Barron)
Date: Mon, 21 May 2007 00:38:38 CST Raw View
Joe Gottman <jgottman@carolina.rr.com> wrote:
> As std::tuple is currently written, it is difficult to impossible to
> write functions that take a tuple of indeterminate size as a parameter
> and make use of the entire tuple. For example, consider a function that
> writes the contents of a tuple to a stream.
>
> template <class... Types>
> inline ostream &print_tuple(ostream &os, const tuple<Types...> &tup);
>
> It is impossible to use a for-loop and tuple::get to write this
> function, because tuple::get<N> requires N to be a compile-time constant
> while a for-loop requires N to be a variable. The programmer would have
> to write an implementation function templated on an int and the Types:
>
gee some simple template programming does this assuming an
std::ostream & operator << (std::ostream &,const T &); exists
for each type in the tuple.
template <int N,int M, class T>
struct printer_unit
{
static std::ostream do_it(std::ostream &os,const T &t)
{
os << std::tr1::get<N>(t);
return printer_unit<N+1,M,T>::do_it(os,t);
}
};
template <int N,class T>
struct printer_unit<N,N,T>
{
static std::ostream & do_it(std::ostream &os,const T &t)
{
return os;
}
};
template <class T>
inline
std::ostream & print_tuple(std::ostream &os,const T &t)
{
return printer_unit
<
0,
std::tr1::tuple_size<T>::value,
T
>::do_it(os,t);
}
and no copies of the tuple are needed. A similiar approach can handle
other iteration of a tuple.
A general approach is to construct an mpl sequence from tuple_size<> and
tuple_element<> and creating a boost::variant from that sequence. You
could convert the tuple to an stl::container of the variant type and
just use ordinary algorithms, providing a functor that handles all types
passed. or just do above and pass get<N>(t) to the functor/function
like above.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Douglas Gregor <doug.gregor@gmail.com>
Date: Mon, 21 May 2007 11:48:37 CST Raw View
On May 20, 5:20 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:
> As std::tuple is currently written, it is difficult to impossible to
> write functions that take a tuple of indeterminate size as a parameter
> and make use of the entire tuple. For example, consider a function that
> writes the contents of a tuple to a stream.
>
> template <class... Types>
> inline ostream &print_tuple(ostream &os, const tuple<Types...> &tup);
>
> It is impossible to use a for-loop and tuple::get to write this
> function, because tuple::get<N> requires N to be a compile-time constant
> while a for-loop requires N to be a variable.
Right. When dealing with tuples, you're going to be doing some
metaprogramming. Without adding some kind of direct metaprogramming
support into the language, I don't think this will change.
> The programmer would have
> to write an implementation function templated on an int and the Types:
> template <int N, class...Types>
> ostream &print_tuple_imp(ostream &os, const tuple<Types...> &tup);
>
> This is overly burdensome for the average programmer, especially since
> the rules of templates require that N be declared first in the template
> list, where it cannot mention the parameter pack Types... .
Well, you could use a defaulted template argument to get I:
template <int I = 0, class... Types>
inline ostream &print_tuple(ostream &os, const tuple<Types...>
&tup);
Still metaprogramming, and you'll still need a basis case, but at
least there's no helper function.
> Note that the definition of operator <() on tuples already uses the
> tail() function, even though it is careful not to name it as such.
Which definition? Implementers are free to define tuple however they
want. In fact, one of my favorite implementations of tuple doesn't
have an explicit "tail". It ends up with "tuple" inheriting from N
base classes tuple_value<0, T0>, tuple_value<1, T1>, tuple_value<2,
T2>, ..., tuple_value<N-1, TN-1>, all as peers. In this case, there is
no tail, because the inheritance hierarchy is very wide (but only 1
level deep).
> Another simple function on tuples that is difficult to write as tuple is
> currently defined is a function that concatenates two tuples
>
> template <class ...Types1, class ... Types2>
> inline tuple<Types1..., Types2...>
> concatenate(const tuple<Types1...> &tup1, const tuple<Types2...> &tup2);
Yep, that's difficult to write.
> useful to add two templated constructors to tuple. The first is a
> constructor that takes a scalar object and a tuple of size one less than
> the size of the tuple we are constructing. It would initialize the head
> of the tuple with the scalar object and the tail with the tuple. Using
> this constructor and constructor forwarding, it is possible to define a
> constructor that takes two tuples such that the sum of their sizes
> equals the size of the tuple we are constructing.
This is a neat idea. I'm a bit concerned about ambiguities... if one
does this, for example, what happens?
tuple<tuple<int>, tuple<float>> t(make_tuple(17l),
make_tuple(3.14159));
There are other such ambiguities when the tuple contains tuples.
> Finally, even though it is not strictly necessary, it is probably a good
> idea to define a head() member function for tuples that returns get<0>,
> simply to aid in programmer understanding of the tail() function.
I don't agree with the addition of tail(), because it puts too many
restrictions on implementers and makes it impossible to use a
particularly good implementation technique. So, head() shouldn't be
there either.
These constructors...
> template <class U, typename... UTypes>
> tuple(U &&u, const tuple<UTypes...> &tup); // iff sizeof...(Types) ==
> sizeof...(UTypes) + 1
>
> template <class U, typename... UTypes>
> tuple(U &&u, tuple<UTypes...> &&tup); // iff sizeof...(Types) ==
> sizeof...(UTypes) + 1
are just a special case of these constructors, right?
> template <class UTypes..., class VTypes...>
> tuple(const tuple<UTypes> &tup1, const tuple<VTypes> &tup2); // iff
> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>
> template <class UTypes..., class VTypes...>
> tuple(const tuple<UTypes> &tup1, tuple<VTypes> &&tup2); // iff
> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>
> template <class UTypes..., class VTypes...>
> tuple(tuple<UTypes> &&tup1, const tuple<VTypes> &tup2); // iff
> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>
> template <class UTypes..., class VTypes...>
> tuple(tuple<UTypes> &&tup1, tuple<VTypes> &&tup2); // iff
> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
If so, do we really need the former (U, tuple<UTypes...>)
constructors?
Anyway, I really like the concatenating constructors. They fill a real
need and make a lot of sense. But head/tail put too many restrictions
on implementers, and aren't really necessary, so I think we should
skip those.
To make this happen for C++0x, I suggest that you turn your post into
a short paper for the C++ committee. Include rationale, a discussion
of the ambiguity issue I mentioned above (including how to (re)solve
it!), and the proposed wording you already gave. Assuming they can
find the time, I think the Library Working Group will agree with this
change.
- Doug
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Joe Gottman <jgottman@carolina.rr.com>
Date: Mon, 21 May 2007 11:47:48 CST Raw View
Carl Barron wrote:
> gee some simple template programming does this assuming an
> std::ostream & operator << (std::ostream &,const T &); exists
> for each type in the tuple.
>
> template <int N,int M, class T>
> struct printer_unit
> {
> static std::ostream do_it(std::ostream &os,const T &t)
> {
> os << std::tr1::get<N>(t);
> return printer_unit<N+1,M,T>::do_it(os,t);
> }
> };
>
> template <int N,class T>
> struct printer_unit<N,N,T>
> {
> static std::ostream & do_it(std::ostream &os,const T &t)
> {
> return os;
> }
> };
>
> template <class T>
> inline
> std::ostream & print_tuple(std::ostream &os,const T &t)
> {
> return printer_unit
> <
> 0,
> std::tr1::tuple_size<T>::value,
> T
> >::do_it(os,t);
> }
>
> and no copies of the tuple are needed. A similiar approach can handle
> other iteration of a tuple.
>
> A general approach is to construct an mpl sequence from tuple_size<> and
> tuple_element<> and creating a boost::variant from that sequence. You
> could convert the tuple to an stl::container of the variant type and
> just use ordinary algorithms, providing a functor that handles all types
> passed. or just do above and pass get<N>(t) to the functor/function
> like above.
I agree that this is will work, but this is a heck of a lot of
machinery to require just to print out a tuple. Also, it is not
reusable, so clients would have to repeat the pattern to do something
else with the entire tuple. And as for copying, in every implementation
of tuple I have seen a tuple<X, Y, Z> contains an object of tuple<Y, Z>
internally, either by inheritance or as a member element, so there
should not be any copying necessary in the implementation of tail().
Joe Gottman
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: cbarron413@adelphia.net (Carl Barron)
Date: Mon, 21 May 2007 22:51:58 GMT Raw View
In article <46517ea1$0$15148$4c368faf@roadrunner.com>, Joe Gottman
<jgottman@carolina.rr.com> wrote:
\
> > A general approach is to construct an mpl sequence from tuple_size<> and
> > tuple_element<> and creating a boost::variant from that sequence. You
> > could convert the tuple to an stl::container of the variant type and
> > just use ordinary algorithms, providing a functor that handles all types
> > passed. or just do above and pass get<N>(t) to the functor/function
> > like above.
>
>
> I agree that this is will work, but this is a heck of a lot of
> machinery to require just to print out a tuple. Also, it is not
> reusable, so clients would have to repeat the pattern to do something
> else with the entire tuple. And as for copying, in every implementation
> of tuple I have seen a tuple<X, Y, Z> contains an object of tuple<Y, Z>
> internally, either by inheritance or as a member element, so there
> should not be any copying necessary in the implementation of tail().
>
TR1 and n2134.pdf do not require a lisp like structure for
std::tr1::tuple<T1,...TN>. As such I do not see a defect.
The example is not written to be a library component. It is possible to
write the unit classes to have a functor template parameter and call
that functor with the result of tr1::get<N>(), in a similiar approach
further its not that complicated an algorithm.
for about 100 lines of code you can provide a tuple_view of the tuple
using boost::mpl then mpl::for_each() can handle any functor that
handles all the distinct types of your tuple.
boost::fusion is a possible replacement, but it is not widely known or
used at this time.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: jgottman@carolina.rr.com (Joe Gottman)
Date: Mon, 21 May 2007 23:33:47 GMT Raw View
Douglas Gregor wrote:
> On May 20, 5:20 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:
>> Note that the definition of operator <() on tuples already uses the
>> tail() function, even though it is careful not to name it as such.
>
> Which definition? Implementers are free to define tuple however they
> want. In fact, one of my favorite implementations of tuple doesn't
> have an explicit "tail". It ends up with "tuple" inheriting from N
> base classes tuple_value<0, T0>, tuple_value<1, T1>, tuple_value<2,
> T2>, ..., tuple_value<N-1, TN-1>, all as peers. In this case, there is
> no tail, because the inheritance hierarchy is very wide (but only 1
> level deep).
>
The current working paper says the following in the definition of
tuple::operator<()
Returns: The result of a lexicographical comparison between t and u
The result is defined as: (bool)(get<0>(t)< get<0>(u)) ||
(!(bool)(get<0>(u) < get<0>(t)) && ttail < utail), where rtail for some
tuple r is a
tuple containing all but the first element of r. For any two zero-length
tuples e and f, e < f returns false.
I didn't realize you could do the inheritance with the multiple
tuple_value<X, N>. Given that, it looks like there's no way to implement
a tail() function without unduly restricting implementation.
>> Another simple function on tuples that is difficult to write as tuple is
>> currently defined is a function that concatenates two tuples
>>
>> template <class ...Types1, class ... Types2>
>> inline tuple<Types1..., Types2...>
>> concatenate(const tuple<Types1...> &tup1, const tuple<Types2...> &tup2);
>
> Yep, that's difficult to write.
>
>> useful to add two templated constructors to tuple. The first is a
>> constructor that takes a scalar object and a tuple of size one less than
>> the size of the tuple we are constructing. It would initialize the head
>> of the tuple with the scalar object and the tail with the tuple. Using
>> this constructor and constructor forwarding, it is possible to define a
>> constructor that takes two tuples such that the sum of their sizes
>> equals the size of the tuple we are constructing.
>
> This is a neat idea. I'm a bit concerned about ambiguities... if one
> does this, for example, what happens?
>
> tuple<tuple<int>, tuple<float>> t(make_tuple(17l),
> make_tuple(3.14159));
You end up with tuple<tuple<int>, tuple<float>>. make_tuple<X, Y>
unambiguously creates an object of type tuple<X, Y>.
>
> There are other such ambiguities when the tuple contains tuples.
>
>> Finally, even though it is not strictly necessary, it is probably a good
>> idea to define a head() member function for tuples that returns get<0>,
>> simply to aid in programmer understanding of the tail() function.
>
> I don't agree with the addition of tail(), because it puts too many
> restrictions on implementers and makes it impossible to use a
> particularly good implementation technique. So, head() shouldn't be
> there either.
>
> These constructors...
>
>> template <class U, typename... UTypes>
>> tuple(U &&u, const tuple<UTypes...> &tup); // iff sizeof...(Types) ==
>> sizeof...(UTypes) + 1
>>
>> template <class U, typename... UTypes>
>> tuple(U &&u, tuple<UTypes...> &&tup); // iff sizeof...(Types) ==
>> sizeof...(UTypes) + 1
>
> are just a special case of these constructors, right?
>
>> template <class UTypes..., class VTypes...>
>> tuple(const tuple<UTypes> &tup1, const tuple<VTypes> &tup2); // iff
>> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>>
>> template <class UTypes..., class VTypes...>
>> tuple(const tuple<UTypes> &tup1, tuple<VTypes> &&tup2); // iff
>> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>>
>> template <class UTypes..., class VTypes...>
>> tuple(tuple<UTypes> &&tup1, const tuple<VTypes> &tup2); // iff
>> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>>
>> template <class UTypes..., class VTypes...>
>> tuple(tuple<UTypes> &&tup1, tuple<VTypes> &&tup2); // iff
>> sizeof...(Types) == sizeof...(UTypes) + sizeof...(VTypes)
>
> If so, do we really need the former (U, tuple<UTypes...>)
> constructors?
>
> Anyway, I really like the concatenating constructors. They fill a real
> need and make a lot of sense. But head/tail put too many restrictions
> on implementers, and aren't really necessary, so I think we should
> skip those.
>
> To make this happen for C++0x, I suggest that you turn your post into
> a short paper for the C++ committee. Include rationale, a discussion
> of the ambiguity issue I mentioned above (including how to (re)solve
> it!), and the proposed wording you already gave. Assuming they can
> find the time, I think the Library Working Group will agree with this
> change.
>
Thanks. Do you think it would be a good idea to define
concatenation as a tuple::operator+() function? It would be similar to
std::string::operator+() and very useful when used in conjunction with
an auto-declared variable.
Joe Gottman
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: cppljevans@suddenlink.net (Larry Evans)
Date: Tue, 22 May 2007 16:14:07 GMT Raw View
On 05/21/2007 12:48 PM, Douglas Gregor wrote:
> On May 20, 5:20 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:
[snip]
>> Note that the definition of operator <() on tuples already uses the
>> tail() function, even though it is careful not to name it as such.
>
> Which definition? Implementers are free to define tuple however they
> want. In fact, one of my favorite implementations of tuple doesn't
> have an explicit "tail". It ends up with "tuple" inheriting from N
> base classes tuple_value<0, T0>, tuple_value<1, T1>, tuple_value<2,
> T2>, ..., tuple_value<N-1, TN-1>, all as peers. In this case, there is
> no tail, because the inheritance hierarchy is very wide (but only 1
> level deep).
>
[snip]
>> Finally, even though it is not strictly necessary, it is probably a good
>> idea to define a head() member function for tuples that returns get<0>,
>> simply to aid in programmer understanding of the tail() function.
>
> I don't agree with the addition of tail(), because it puts too many
> restrictions on implementers and makes it impossible to use a
> particularly good implementation technique. So, head() shouldn't be
> there either.
[snip]
What about:
enum
tuple_implementation
{ tuple_list
, tuple_vec
, tuple_inherit
};
template
< class Types...
, tuple_implementation Impl=tuple_list
>
struct
tuple
;
The standard could require implementer's to at least provide
the above elements of tuple_implementation but allow
additions. The default would be like todays with the
addition of head and tail functions, tuple_vec would be
like fusion's (saving space), and the last would be
Douglas' favorite inherit version.
Of course there's the small problem that the current variadic
template compiler only allows Types... at the end of the
template formal parameter list :(
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Douglas Gregor <doug.gregor@gmail.com>
Date: Tue, 22 May 2007 11:13:49 CST Raw View
On May 21, 7:33 pm, jgott...@carolina.rr.com (Joe Gottman) wrote:
> >> useful to add two templated constructors to tuple. The first is a
> >> constructor that takes a scalar object and a tuple of size one less than
> >> the size of the tuple we are constructing. It would initialize the head
> >> of the tuple with the scalar object and the tail with the tuple. Using
> >> this constructor and constructor forwarding, it is possible to define a
> >> constructor that takes two tuples such that the sum of their sizes
> >> equals the size of the tuple we are constructing.
>
> > This is a neat idea. I'm a bit concerned about ambiguities... if one
> > does this, for example, what happens?
>
> > tuple<tuple<int>, tuple<float>> t(make_tuple(17l),
> > make_tuple(3.14159));
>
> You end up with tuple<tuple<int>, tuple<float>>. make_tuple<X, Y>
> unambiguously creates an object of type tuple<X, Y>.
The ambiguity is in the construction of tuple<tuple<int>,
tuple<float>>. With the addition of your concatenating constructor, it
has:
tuple(tuple<int>, tuple<float>);
template<typename... TTypes, typename... UTypes>
tuple(tuple<TTypes...> const& x, tuple<UTypes...> const& y);
Which constructor gets called for my example above? Is it the
constructor you expected? Are there any cases where the "wrong"
constructor gets called, meaning that we need to say something about
the interaction of these constructors? If there aren't any such cases,
why not?
> Thanks. Do you think it would be a good idea to define
> concatenation as a tuple::operator+() function? It would be similar to
> std::string::operator+() and very useful when used in conjunction with
> an auto-declared variable.
I would advise against proposing operator+. At this point, gettng new
functionality into C++0x is a long-shot, no matter how small the
proposal. Overloading an operator in a non-traditional way is an easy
way to sink an otherwise good proposal.
- Doug
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: jgottman@carolina.rr.com (Joe Gottman)
Date: Wed, 23 May 2007 05:22:33 GMT Raw View
Douglas Gregor wrote:
>
> The ambiguity is in the construction of tuple<tuple<int>,
> tuple<float>>. With the addition of your concatenating constructor, it
> has:
>
> tuple(tuple<int>, tuple<float>);
>
> template<typename... TTypes, typename... UTypes>
> tuple(tuple<TTypes...> const& x, tuple<UTypes...> const& y);
>
> Which constructor gets called for my example above? Is it the
> constructor you expected? Are there any cases where the "wrong"
> constructor gets called, meaning that we need to say something about
> the interaction of these constructors? If there aren't any such cases,
> why not?
My proposed concatenating constructor would require that
corresponding parameters fulfill the Convertible concept. If you have
two types U and V such that V has an explicit constructor that takes a
parameter of type U, is U considered to be Convertible to V? If not,
then the example you site will unambiguously call the first constructor,
because the constructor tuple<int>::tuple(const int &) is defined to be
explicit. If U is considered to be convertible to V when an explicit
constructor is used, then I will have to think about the problem a
little more.
Joe Gottman
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]