Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Different begin and end types in range-based for

114 views
Skip to first unread message

Andy Lutomirski

unread,
May 3, 2012, 2:10:51 PM5/3/12
to
I'm not sure whether this is should be submitted as a DR or if I should
just live with it, but it would be convenient to have more flexibility
in implementing containers with range-based for.

6.5.4 [stmt.ranged] defines this statement:

for ( for-range-declaration : expression ) statement

as equivalent to:


{
auto && __range = digits();
for ( auto __begin = begin-expr,
__end = end-expr;
__begin != __end;
++__begin ) {
auto i = *__begin;
statement
}
}

There is nothing in this definition that fundamentally requires that
__begin and __end have the same type. However, 7.1.6.4 [dcl.spec.auto]
says:

7. If the list of declarators contains more than one declarator, the
type of each declared variable is determined
as described above. If the type deduced for the template parameter U is
not the same in each deduction, the
program is ill-formed.

Therefore, if the return types of begin-expr and end-expr are different,
the program is ill-formed. An alternative formulation that would be
identical other than this restriction (as far as I can tell) would be:

{
auto && __range = digits();
auto __begin = begin-expr;
auto __end = end-expr;
for ( ;
__begin != __end;
++__begin ) {
auto i = *__begin;
statement
}
}

For example:

#include <iostream>

struct digit_end {};

struct digit_iter
{
int i;
void operator ++ () { ++i; }
int operator * () const { return i; }
bool operator != (digit_end) const { return i != 10; }
};

struct digits
{
digit_iter begin() const { digit_iter it; it.i = 0; return it; }
digit_end end() const { return digit_end(); }
};

int main(int, char **)
{
using namespace std;

// for (auto i : digits()) cout << i << endl; [ill-formed]

// Alternative formulation, which works in g++ 4.6
{
auto && __range = digits();
auto __begin = begin(__range);
auto __end = end(__range);
for ( ;
__begin != __end;
++__begin ) {
auto i = *__begin;
cout << i << endl;
}
}

return 0;
}


It seems to me that 6.5.4 [stmt.ranged] should either be changed to
allow this use or should contain an example or explanatory note
indicating that this use is explicitly disallowed.

This particular example is silly, but I have a container in real code
for which there is no efficient way to implement a true end() function,
but checking whether at iterator is at the end is very simple.

(This idea is not new. The same issue is mentioned in
http://cxxpanel.org.uk/ballotcomment/526 but AFAICT was never submitted
anywhere.)

Thanks,
Andy


--
[ comp.std.c++ is moderated. To submit articles, try posting with your ]
[ newsreader. If that fails, use mailto:std-cpp...@vandevoorde.com ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]

Daniel Krügler

unread,
May 4, 2012, 2:23:24 PM5/4/12
to
Am 03.05.2012 20:10, schrieb Andy Lutomirski:
> I'm not sure whether this is should be submitted as a DR or if I should
> just live with it, but it would be convenient to have more flexibility
> in implementing containers with range-based for.

This issue was discussed before C++11 was released, see below for details.

> 6.5.4 [stmt.ranged] defines this statement:
>
> for ( for-range-declaration : expression ) statement
>
> as equivalent to:
>
>
> {
> auto&& __range = digits();
> for ( auto __begin = begin-expr,
> __end = end-expr;
> __begin != __end;
> ++__begin ) {
> auto i = *__begin;
> statement
> }
> }
>
> There is nothing in this definition that fundamentally requires that
> __begin and __end have the same type. However, 7.1.6.4 [dcl.spec.auto]
> says:
>
> 7. If the list of declarators contains more than one declarator, the
> type of each declared variable is determined
> as described above. If the type deduced for the template parameter U is
> not the same in each deduction, the
> program is ill-formed.
>
> Therefore, if the return types of begin-expr and end-expr are different,
> the program is ill-formed. An alternative formulation that would be
> identical other than this restriction (as far as I can tell) would be:
>
> {
> auto&& __range = digits();
> auto __begin = begin-expr;
> auto __end = end-expr;
> for ( ;
> __begin != __end;
> ++__begin ) {
> auto i = *__begin;
> statement
> }
> }
>
> For example:
>
> #include<iostream>
>
> struct digit_end {};
>
> struct digit_iter
> {
> int i;
> void operator ++ () { ++i; }
> int operator * () const { return i; }
> bool operator != (digit_end) const { return i != 10; }
> };
>
> struct digits
> {
> digit_iter begin() const { digit_iter it; it.i = 0; return it; }
> digit_end end() const { return digit_end(); }
> };
>
> int main(int, char **)
> {
> using namespace std;
>
> // for (auto i : digits()) cout<< i<< endl; [ill-formed]
>
> // Alternative formulation, which works in g++ 4.6
> {
> auto&& __range = digits();
> auto __begin = begin(__range);
> auto __end = end(__range);
> for ( ;
> __begin != __end;
> ++__begin ) {
> auto i = *__begin;
> cout<< i<< endl;
> }
> }
>
> return 0;
> }
>
>
> It seems to me that 6.5.4 [stmt.ranged] should either be changed to
> allow this use or should contain an example or explanatory note
> indicating that this use is explicitly disallowed.
>
> This particular example is silly, but I have a container in real code
> for which there is no efficient way to implement a true end() function,
> but checking whether at iterator is at the end is very simple.
>
> (This idea is not new. The same issue is mentioned in
> http://cxxpanel.org.uk/ballotcomment/526 but AFAICT was never submitted
> anywhere.)

This issue *was* submitted, this is national body comment GB 27, see

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3296.html

or here:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3289.pdf

At that time the range-for-loop was intensively discussed and no
consensus was found for this change. One further reason for not applying
this change was because the library algorithms also use a homogeneous
iterator model and the same applied to the concept models that were
considered just before.

Now, after the dust has settled and real implementations do exist, there
might be a good time to consider an extension of the current rules. If
you are interested in this I suggest to work on a proposal for that new
feature.

HTH& Greetings from Bremen,

Daniel Krügler

Andy Lutomirski

unread,
May 4, 2012, 5:51:05 PM5/4/12
to
How do I go about submitting a proposal like that?

This idea is made a bit uglier by the fact (that I mis-remebered) that
begin(__range) and end(__range) are tried after .begin() and .end(). I
had imagined a container that implemented C++98-style .begin() and
.end() but had non-member begin and end that used different types and
were more efficient.

This can add to my fun as I masochistically try to maintain a decently
large code base in a language that just got standardized using compilers
that are a little bit behind :)

--Andy

Andy Lutomirski

unread,
May 5, 2012, 2:01:23 AM5/5/12
to
[Apologies if this is a duplicate. My mail client messed up on the
first try.]

On 05/04/2012 11:23 AM, Daniel Krügler wrote:
How do I go about submitting a proposal like that?

This idea is made a bit uglier by the fact (that I mis-remebered) that
begin(__range) and end(__range) are tried after .begin() and .end(). I
had imagined a container that implemented C++98-style .begin() and
.end() but had non-member begin and end that used different types and
were more efficient.

This can add to my fun as I masochistically try to maintain a decently
large code base in a language that just got standardized using compilers
that are a little bit behind :)

--Andy


0 new messages