Topic: Different begin and end types in range-based for


Author: Andy Lutomirski <luto@amacapital.net>
Date: Thu, 3 May 2012 11:10:51 -0700 (PDT)
Raw View
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-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]




Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?=<daniel.kruegler@googlemail.com>
Date: Fri, 4 May 2012 11:23:24 -0700 (PDT)
Raw View
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




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




Author: Andy Lutomirski<luto@amacapital.net>
Date: Fri, 4 May 2012 14:51:05 -0700 (PDT)
Raw View
On 05/04/2012 11:23 AM, Daniel Kr   gler wrote:
>  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.

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


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




Author: Andy Lutomirski <luto@amacapital.net>
Date: Fri, 4 May 2012 23:01:23 -0700 (PDT)
Raw View
[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:
> 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.

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


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