2

I am attempting to create a class that wraps another template-specified type, like a Box. I also want to add operators to enable comparing Box('s stored value) against any type that its stored value is comparable to. A requires clause is used to check that the comparison is valid. Here is a simple example:

template <class T> struct Box {
    T val;
    
    template <class... Args> Box(Args&&... args)
        : val(std::forward<Args>(args)...) {}
    
    template <class O> bool operator==(const O& b) const
        requires requires { val == b; } { return val == b; }
};

int main() {
    Box<int> a(5);
    a == 5;
}

This compiles and runs fine with operator== as a member function. However, if I rewrite the operator== as a friend function, it falls apart:

//...
    template <class O> friend bool operator==(const Box& a, const O& b)
        requires requires { a.val == b; } { return a.val == b; }
//...

When compiled on GCC:

<source>: In substitution of 'template<class O> bool operator==(const Box<int>&, const O&) requires requires{operator==::a->val == operator==::b;} [with O = int]':
<source>:14:29:   required by substitution of 'template<class O> bool operator==(const Box<int>&, const O&) requires requires{operator==::a->val == operator==::b;} [with O = int]'
<source>:20:7:   required from here
<source>:13:33:   required by the constraints of 'template<class T> template<class O> bool operator==(const Box<T>&, const O&) requires requires{operator==::a->val == operator==::b;}'
<source>:14:12:   in requirements  [with T = int; O = Box<int>]
<source>:14:26: error: satisfaction of atomic constraint 'requires{operator==::a->val == operator==::b;} [with T = T; O = O]' depends on itself
   14 |                 requires requires { a.val == b; } { return a.val == b; }
      |                          ^~~~~~~~~~~~~~~~~~~~~~~~

This appears on all the most recent versions of GCC, in both C++20 and C++23 modes. Clang gives a similar error. The same problem also occurs (albeit with a longer error message) if I use a concept instead of a requires expression, such as std::equality_comparable_with. The only way to fix this error is to not check the validity of the comparison expression in the requires clause.

It seems that the compiler is considering the enclosing operator function in the overload set of the requires constraint, causing the operator to be infinitely instantiated recursively. But I don't quite get how that is possible? I thought friend functions could only be found by ADL, and neither of the operands in the requires expression is a Box (in this instantiation, both are int), which should additionally fail due to type argument mismatch, and the fact that int doesn't have a member val (after the first recursion). Can someone explain what is going on, and if possible, how this can be fixed?

paleonix
  • 2,293
  • 1
  • 13
  • 29
  • 2
    Since `Box`'s constructor is not constrained, it can be implicitly constructed by `int`, which allows the template parameter `O` to be instantiated as `Box`. In the `requires`-clause, we check if a `Box` can be compared with an `int`, which again instantiates `O` as `Box`, and then we enter the `requires`-clause to check again... – 康桓瑋 Apr 28 '23 at 02:34
  • The error occurs even if the constructor is explicit. Besides, why would the compiler deduce O as `Box` over `int` when the argument is `int`? – Da Spud Lord Apr 28 '23 at 04:18
  • 1
    "*The error occurs even if the constructor is explicit.*" Probably a bug of gcc, [clang works fine](https://godbolt.org/z/9bYY3jYhM). – 康桓瑋 Apr 28 '23 at 06:09

0 Answers0