GCC with c++17 standard cannot compile range-based for case with different begin/end data type

Viewed 120

For C++17, it has a small change with proposal p0184r0,which make it possible to specify different begin() and end() type for a user-defined type, to compatible with range-based for. But it seems gcc will failure on following cases:

#include<iterator>

#include<iostream>

using namespace std;

class read_t
{
    public:
        using data_type = int;
        read_t()
        {
        }


        ~read_t()
        {
        }

        struct end_marker {};
        class iterator
        {
            public:
                using iterator_category = std::input_iterator_tag;
                using value_type = data_type;
                using difference_type = long int;

                iterator(read_t& reader)
                    : m_reader(reader)
                {}
                data_type operator*()
                {
                    return m_i;
                }
                iterator& operator++()
                {
                    return *this;
                }
                bool operator!=(end_marker&)
                {
                    m_i ++;
                    return m_i <= 3;
                }
            private:
                read_t& m_reader;
                int m_i = 0;
        };

        iterator begin() { return iterator(*this); }
        end_marker end() { return end_marker{}; }
};

int main()
{
    using namespace std;
    for (auto i : read_t())
    {
        cout << i << endl;
    }
}

with error message as:

~/test >>> g++ -std=c++17 -o b0 b.cpp
b.cpp: In function ‘int main()’:
b.cpp:56:26: error: inconsistent begin/end types in range-based ‘for’ statement: ‘read_t::iterator’ and ‘read_t::end_marker’
   56 |     for (auto i : read_t())
      |                          ^
~/test >>>

My gcc compiler is gcc 10.2.0, on Manjaro platform. The same code is passed with clang 11.0.1 and vs 2019.

1 Answers

This is a compiler bug as far as I can tell. If you replace the range-for loop with its non-range equivalent, as per the standard, it compiles:

 {
                                       // init-statement
    auto &&range = read_t() ;          // auto &&range = for-range-initializer ;
    auto begin   = std::begin(range) ; // auto begin   = begin-expr ;
    auto end     = std::end(range) ;   // auto end     = end-expr ;
    for ( ; begin != end; ++begin ) {
        auto i = * begin ;             // for-range-declaration = *__begin ;
        cout << i << endl;             // statement
    }
}

A workaround seems to be to declare the argument of operator!= to be a reference to const. Which should have been done in the first place since the referred object isn't modified. Alternatively, use a value argument - this would theoretically be more efficient.

Related