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:
Foo(T)
explicit, or providing a constructor Foo(std::vector<Foo>)
solves the problemint
or std::vector<int>
) solves the problemWith 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.