Search code examples
c++iteratorc++20c++23

"atomic constraint depends on itself" error when compiling iterator in C++23


I'm trying to make an iterator that passes over a string view mostly character-by-character with a few special cases and what I tried was the following:

// iter.hpp
#include <string_view>

typedef std::basic_string_view<char> UStringView;

class symbol_iter
{
private:
  UStringView str;
  UStringView::size_type sloc = 0;
  UStringView::size_type eloc = 0;
public:
  symbol_iter(UStringView s);
  symbol_iter(const symbol_iter& other);
  ~symbol_iter();
  UStringView operator*();
  symbol_iter& operator++();
  bool operator!=(const symbol_iter& other) const;
  bool operator==(const symbol_iter& other) const;
  symbol_iter begin();
  symbol_iter end();
};

symbol_iter::symbol_iter(UStringView s) : str(s)
{
  ++*this;
}

symbol_iter::symbol_iter(const symbol_iter& other)
  : str(other.str), sloc(other.sloc), eloc(other.eloc) {}

symbol_iter::~symbol_iter() {}

UStringView symbol_iter::operator*() {
  return str.substr(sloc, eloc-sloc);
}

symbol_iter& symbol_iter::operator++()
{
  if (sloc < str.size()) {
    sloc = eloc;
    eloc++;
    char c = str[sloc];
    if (c == '\\') {
      sloc++;
      eloc++;
    } else if (c == '<') {
      for (auto i = eloc; i < str.size(); i++) {
        if (str[i] == '>') {
          eloc = i + 1;
          break;
        }
      }
    }
    if (eloc > str.size()) eloc = str.size();
  }
  return *this;
}

bool symbol_iter::operator!=(const symbol_iter& o) const
{
  return str != o.str || sloc != o.sloc || eloc != o.eloc;
}

bool symbol_iter::operator==(const symbol_iter& o) const
{
  return str == o.str && sloc == o.sloc && eloc == o.eloc;
}

symbol_iter symbol_iter::begin()
{
  return symbol_iter(str);
}

symbol_iter symbol_iter::end()
{
  symbol_iter ret(str);
  ret.sloc = str.size();
  ret.eloc = str.size();
  return ret;
}

// main.cc
#include "iter.hpp"
#include <iostream>

int main(int argc, char** argv) {
  UStringView name(argv[1]);
  for (auto sym : symbol_iter(name)) {
    std::cout << sym << std::endl;
  }
}

When I compile this under C++2a, it does exactly what I expect:

$ g++ --std=c++2a main.cc
$ ./a.out 'xyz<q>'
x
y
z
<q>

but when I compile with C++23, I get the error

/usr/include/c++/11/bits/ranges_base.h:574:21: error: satisfaction of atomic constraint ‘requires(_Tp& __t) {std::ranges::__cust::begin(__t);std::ranges::__cust::end(__t);} [with _Tp = _Range]’ depends on itself
  574 |     concept range = requires(_Tp& __t)
      |                     ^~~~~~~~~~~~~~~~~~
  575 |       {
      |       ~              
  576 |         ranges::begin(__t);
      |         ~~~~~~~~~~~~~~~~~~~
  577 |         ranges::end(__t);
      |         ~~~~~~~~~~~~~~~~~
  578 |       };
      |       ~              

and I have not been able to find any explanation of this error by googling.

The loop in main shows how I have been using it (range-for loops with auto), though the error appears even if I remove the loop (that is, even if nothing else in the code refers to the symbol_iter class at all).

Additionally, the error is fixed if I change UStringView to UStringView* everywhere except operator* (I tried this because elsewhere in the codebase there is an iterator with an identical interface except that the input type is a pointer rather than a view).


Solution

  • It would appear, based on the comments from ildjarn and 康桓瑋 that this is a bug in a prior version of GCC.

    The more fundamental problem, which I think Nicol Bolas was trying to point out (and which I did not understand the first few times), is that I shouldn't try to create multiple iterator types for a single class (much as I would like to), a mistake that hadn't been caught before because it happened to be the case that my prior attempts at writing iterators didn't break anything.

    The class which now works on both compiler versions is

    class symbol_iter
    {
    private:
      UStringView str;
    public:
      symbol_iter(UStringView s) : str(s) {}
      ~symbol_iter() {}
      class iterator
      {
        friend symbol_iter;
      private:
        UStringView str;
        UStringView::size_type sloc = 0;
        UStringView::size_type eloc = 0;
      public:
        iterator(UStringView s);
        iterator(const iterator& other);
        ~iterator();
        UStringView operator*() const;
        iterator& operator++();
        bool operator!=(const symbol_iter::iterator& other) const;
        bool operator==(const symbol_iter::iterator& other) const;
      };
      iterator begin() const;
      iterator end() const;
    };