Re: [boost] Report from Berlin C++ Standards Committee meeting

Robert Ramey wrote:
On the other hand - I don't see the motivation for including concepts in the core language. Haven't we been able to implement concept checking with the current facilities of the library? It seems to me that the main problem these days with the C++ language these days is that its too hard to write a correct compiler for it. Making the core language fancier will only make this problem worse.
Concepts in the language allow things that aren't currently possible with C++98. For example, standard algorithms of the form: fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) cannot have overloads (in C++98) taking containers/ranges: fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f ) as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
While, the commitee is at it - how about re-visiting two-phase lookup. Apparently there are enough implementers skeptical about it that they've declined to implement it. (for good reason in my opinion). I believe this is related to the "export" feature - which has also failed to gain traction.
export is way too complicated to implement, including changes to the output the compiler generates and how the linker works. Two-phase lookup has been implemented by a growing number of compiler vendors. The reason why some vendors choose not to implement this is that it will break (a lot of?) existing code.
So its seems to me that there are a number of issues more important/urgent than the ones currently being focused on.
I, for one, like concepts. They also help simplify the implementation of functions like std::distance as they remove the need to use iterator_traits and the iterator categories that clutter implementations and make them harder to read. - Reece _________________________________________________________________ Express yourself instantly with MSN Messenger! Download today it's FREE! http://messenger.msn.click-url.com/go/onm00200471ave/direct/01/

Reece Dunn wrote:
Robert Ramey wrote:
On the other hand - I don't see the motivation for including concepts in the core language. Haven't we been able to implement concept checking with the current facilities of the library? It seems to me that the main problem these days with the C++ language these days is that its too hard to write a correct compiler for it. Making the core language fancier will only make this problem worse.
Concepts in the language allow things that aren't currently possible with C++98. For example, standard algorithms of the form:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f )
cannot have overloads (in C++98) taking containers/ranges:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f )
as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
you can use enable_if on the latter and disable it the two types are the same. -Thorsten

Thorsten Ottosen <thorsten.ottosen@dezide.com> writes:
cannot have overloads (in C++98) taking containers/ranges:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f )
as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
you can use enable_if on the latter and disable it the two types are the same.
Not if you happen to have a type that is both a valid range and a valid function object. Yes, that's a corner case, but it's the corner of a large floating block of ice. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On 4/11/06, David Abrahams <dave@boost-consulting.com> wrote:
Thorsten Ottosen <thorsten.ottosen@dezide.com> writes:
cannot have overloads (in C++98) taking containers/ranges:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f )
as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
you can use enable_if on the latter and disable it the two types are the same.
Not if you happen to have a type that is both a valid range and a valid function object.
Yes, that's a corner case, but it's the corner of a large floating block of ice.
For this idiom, I use boost::iterator_range like so, fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( boost::iterator_range<Iterator> rng ) fn( boost::iterator_range<Iterator> rng, Functor f ) This is basicly the same thing as section 18.3.1 of Stroustrup's The C++ Programing Language. When calling fn() from templates with parameterized range types, I do something like ... template<typename ForwardRange> void some_other_function(const ForwardRange& x) { using namespace boost; function_requires< ForwardRangeConcept<ForwardRange> >(); // ... fn(make_iterator_range(x), f); } The call to make_iterator_range() is a hassle. And this is just a work-around for the specific iterator/functor/range overload resolution problem. I think in-language support for concepts could deffinitely clean up this code and make life easier. Daniel Walker

"Daniel Walker" <daniel.j.walker@gmail.com> writes:
On 4/11/06, David Abrahams <dave@boost-consulting.com> wrote:
Thorsten Ottosen <thorsten.ottosen@dezide.com> writes:
cannot have overloads (in C++98) taking containers/ranges:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f )
as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
you can use enable_if on the latter and disable it the two types are the same.
Not if you happen to have a type that is both a valid range and a valid function object.
Yes, that's a corner case, but it's the corner of a large floating block of ice.
For this idiom, I use boost::iterator_range like so,
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( boost::iterator_range<Iterator> rng ) fn( boost::iterator_range<Iterator> rng, Functor f )
This is not generic. Now you can't pass other valid Ranges (e.g. std::vector<T>) as the first argument to fn.
This is basicly the same thing as section 18.3.1 of Stroustrup's The C++ Programing Language. When calling fn() from templates with parameterized range types, I do something like ...
template<typename ForwardRange> void some_other_function(const ForwardRange& x) { using namespace boost; function_requires< ForwardRangeConcept<ForwardRange> >(); // ... fn(make_iterator_range(x), f); }
The call to make_iterator_range() is a hassle.
Right.
And this is just a work-around for the specific iterator/functor/range overload resolution problem. I think in-language support for concepts could deffinitely clean up this code and make life easier.
Bigtime. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On 4/15/06, David Abrahams <dave@boost-consulting.com> wrote:
"Daniel Walker" <daniel.j.walker@gmail.com> writes:
On 4/11/06, David Abrahams <dave@boost-consulting.com> wrote:
Thorsten Ottosen <thorsten.ottosen@dezide.com> writes:
cannot have overloads (in C++98) taking containers/ranges:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f )
as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
you can use enable_if on the latter and disable it the two types are the same.
Not if you happen to have a type that is both a valid range and a valid function object.
Yes, that's a corner case, but it's the corner of a large floating block of ice.
For this idiom, I use boost::iterator_range like so,
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( boost::iterator_range<Iterator> rng ) fn( boost::iterator_range<Iterator> rng, Functor f )
This is not generic. Now you can't pass other valid Ranges (e.g. std::vector<T>) as the first argument to fn.
Yeah, I know. This is just a work-around because the generic version isn't possible today due to the overload ambiguity. You can indirectly use the functions with other valid Ranges like vector, but it's a hassle because you first have to convert them using make_iterator_range(). In some circumstances, for the time being, this may be acceptable for this particular idiom (overloading algorithms to take both iterators and ranges), but really I agree that it further illustrates the need for in-language support for concepts in a future version of C++ in order to make the overloads generic. Daniel Walker

On Apr 15, 2006, at 3:06 PM, Daniel Walker wrote:
On 4/15/06, David Abrahams <dave@boost-consulting.com> wrote:
"Daniel Walker" <daniel.j.walker@gmail.com> writes:
On 4/11/06, David Abrahams <dave@boost-consulting.com> wrote:
Thorsten Ottosen <thorsten.ottosen@dezide.com> writes:
cannot have overloads (in C++98) taking containers/ranges:
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( Range rng ) fn( Range rng, Functor f )
as the last overload is ambiguous. Concepts will allow this to be resolved as a Functor will not match the Iterator requirements :).
you can use enable_if on the latter and disable it the two types are the same.
Not if you happen to have a type that is both a valid range and a valid function object.
Yes, that's a corner case, but it's the corner of a large floating block of ice.
For this idiom, I use boost::iterator_range like so,
fn( Iterator first, Iterator last ) fn( Iterator first, Iterator last, Functor f ) fn( boost::iterator_range<Iterator> rng ) fn( boost::iterator_range<Iterator> rng, Functor f )
This is not generic. Now you can't pass other valid Ranges (e.g. std::vector<T>) as the first argument to fn.
Yeah, I know. This is just a work-around because the generic version isn't possible today due to the overload ambiguity. You can indirectly use the functions with other valid Ranges like vector, but it's a hassle because you first have to convert them using make_iterator_range(). In some circumstances, for the time being, this may be acceptable for this particular idiom (overloading algorithms to take both iterators and ranges), but really I agree that it further illustrates the need for in-language support for concepts in a future version of C++ in order to make the overloads generic.
Actually in this particular case, all we need is a more smartly designed std::iterator_traits class: Spec: --- template <class Iterator> struct iterator_traits { }; However if Iterator has the nested types: difference_type, value_type, pointer, reference and iterator_category, and if Iterator::iterator_category (if it exists) is implicitly convertible to either std::input_iterator_tag, or std::output_iterator_tag, then the struct iterator_traits is instead: template <class Iterator> struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; }; Plus the pointer specializations... Plus any user-defined specializations... --- Such an iterator_traits is easy to implement today with has_member_* and is_convertible. And now you can say iterator_traits<any type at all>. The instantiation always compiles and may or may not have the nested typedefs. Then you can make an is_input_iterator trait: A type T is an input iterator if iterator_traits<T> has a member called iterator_category that is convertible to std::input_iterator_tag. enable_if your fn with is_input_iterator<InputIterator>, and you're done. You can get most of the way there with today's iterator_traits. You'll miss user-defined iterators that specialize iterator_traits instead of containing the nested types directly (which is practically no types at all). Just change is_iterator to look for the nested types, and then specialize is_iterator for pointer types. Then you can build is_input_iterator on top of is_iterator. That's not an argument against concepts. Its just a fun and useful exercise to build is_input_iterator<T>. Maybe someone can see a better way to do it with today's iterator_traits than what I've described. -Howard

Howard Hinnant <howard.hinnant@gmail.com> writes:
Actually in this particular case, all we need is a more smartly designed std::iterator_traits class:
Spec: --- template <class Iterator> struct iterator_traits { };
However if Iterator has the nested types: difference_type, value_type, pointer, reference and iterator_category, and if Iterator::iterator_category (if it exists) is implicitly convertible to either std::input_iterator_tag, or std::output_iterator_tag, then the struct iterator_traits is instead:
template <class Iterator> struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; };
Plus the pointer specializations... Plus any user-defined specializations... ---
Such an iterator_traits is easy to implement today with has_member_* and is_convertible. And now you can say iterator_traits<any type at all>. The instantiation always compiles and may or may not have the nested typedefs.
Then you can make an is_input_iterator trait:
A type T is an input iterator if iterator_traits<T> has a member called iterator_category that is convertible to std::input_iterator_tag.
...which will fail even for some types that are legit members of std:: in C++98, like std::iterator<std::input_iterator_tag, int> (false positive), not to mention types that inherit privately from types like the one above, or just have those names as private members (compile-time error). I don't mean to rain on this parade every time you bring up the idea, Howard; it's a nice hack. I just don't want anyone to be confused about that: it's a nice hack that works most of the time... and sometimes breaks. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Apr 16, 2006, at 5:56 PM, David Abrahams wrote:
Howard Hinnant <howard.hinnant@gmail.com> writes:
Actually in this particular case, all we need is a more smartly designed std::iterator_traits class:
Spec: --- template <class Iterator> struct iterator_traits { };
However if Iterator has the nested types: difference_type, value_type, pointer, reference and iterator_category, and if Iterator::iterator_category (if it exists) is implicitly convertible to either std::input_iterator_tag, or std::output_iterator_tag, then the struct iterator_traits is instead:
template <class Iterator> struct iterator_traits { typedef typename Iterator::difference_type difference_type; typedef typename Iterator::value_type value_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; typedef typename Iterator::iterator_category iterator_category; };
Plus the pointer specializations... Plus any user-defined specializations... ---
Such an iterator_traits is easy to implement today with has_member_* and is_convertible. And now you can say iterator_traits<any type at all>. The instantiation always compiles and may or may not have the nested typedefs.
Then you can make an is_input_iterator trait:
A type T is an input iterator if iterator_traits<T> has a member called iterator_category that is convertible to std::input_iterator_tag.
...which will fail even for some types that are legit members of std:: in C++98, like
std::iterator<std::input_iterator_tag, int>
(false positive), not to mention types that inherit privately from types like the one above, or just have those names as private members (compile-time error).
I don't mean to rain on this parade every time you bring up the idea, Howard; it's a nice hack. I just don't want anyone to be confused about that: it's a nice hack that works most of the time... and sometimes breaks.
Sure, but now we're in the area of intentionally breaking the system. Concepts will not be invulnerable to deceptive coding either. There's no structural difference between a single pass iterator and a multi pass iterator for Concepts to check. Unless I'm really missing something, you have to depend upon some kind of declarative statement: "I'm a single pass iterator!" Concepts should make it much easier to build and manipulate types of types, and do a better job of it than just checking a single tag. But I don't see a way to eliminate the need to ultimately trust that some tag really means what we think it means. Put another way, the chances of std::iterator<std::input_iterator_tag, int> being an honest (not test case, not intentional abuse) false positive in the wild for is_input_iterator as I described it is probably somewhere around 0.0001%. Concepts will better those odds by another factor of 100, or maybe even 1000. But won't eliminate the possibility. -Howard
participants (5)
-
Daniel Walker
-
David Abrahams
-
Howard Hinnant
-
Reece Dunn
-
Thorsten Ottosen