Search code examples
c++c++20c++-concepts

Why is the compiler checking this concept and generating an error?


Compiling the following code results in: error: satisfaction of atomic constraint 'requires(T v) {bar(v);} [with T = T]' depends on itself.

#include<iostream>
#include<vector>


template<class T>
concept Barable = requires(T v)
{
    bar(v);
};

struct Foo
{
    template<class T> requires Barable<T>
    Foo(T) {};
};

void bar(Foo) {
    std::cout << "Foo";
};

void bar(std::vector<Foo>) {
    std::cout <<  "vector";
}

int main()
{
    auto v = std::vector<Foo>{};
    bar(v);
}

I understand how checking this concept leads to a circular dependency. What I don't understand is why, with this setup, the compiler gives an error. From my understanding, it should just not add bar(Foo) to the overload set and use bar(std::vector<Foo>). For any other type it seems to work that way.

Notes:


Solution

  • With two-phase name lookup, the name bar within the definition of Barable is looked up at two points - at the point of definition using ordinary lookup, and again at the point of instantiation, using argument-dependent lookup (ADL) only. Since the name bar is not declared before the definition of Barable, the first phase doesn't find anything. So the concept relies entirely on finding bar by ADL at the point of instantiation.

    When you later do

    SomeType arg;
    bar(arg);
    

    the compiler finds bar(Foo) declaration via ordinary lookup, and needs to determine whether it's viable: it could be a call to bar(Foo{arg}) using Foo's converting constructor. So it needs to instantiate that converting constructor, which requires instantiating Barable<SomeType> concept.

    This could go one of two ways. If SomeType is not associated with global namespace (as would be the case with e.g. std::vector<int>, or plain int, or SomeNamespace::SomeType), then ADL doesn't find any declaration of bar; such a type is unambiguously not Barable.

    If SomeType is in fact associated with the global namespace (as would be the case with e.g. vector<Foo>, or Baz, or std::vector<Baz> for some type Baz declared in the global namespace), then bar(Foo) is found. Then as part of instantiating Barable<SomeType>, the compiler needs to instantiate bar(Foo{v}) for v of type SomeType - but recall that we are already in the middle of instantiating exactly that. Hence the error that the concept depends on itself.