Компиляция следующего кода приводит к: 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);
}
Я понимаю, как проверка этой концепции приводит к циклической зависимости. Чего я не понимаю, так это почему при такой настройке компилятор выдает ошибку.
Насколько я понимаю, просто не следует добавлять bar(Foo)
в набор перегрузки и использовать bar(std::vector<Foo>)
. Для любого другого типа это работает именно так.
Примечания:
Foo(T)
явная или предоставление конструктора Foo(std::vector<Foo>)
решает проблемуint
или std::vector<int>
) решает проблемуКажется, компилятор пытается выполнить разрешение перегрузки между bar(std::vector<Foo>)
и bar(Foo)
. Последний теоретически можно вызвать через bar(Foo{v})
, используя конструктор преобразования Foo
. Для этого компилятору необходимо определить, является ли Foo{v}
жизнеспособным, а это зависит от того, является ли vector<Foo>
Barable
, а это, в свою очередь, требует проверки bar(v)
на наличие v
типа std::vector<Foo>
, что завершает круг.
@ИгорьТандетник Согласен. Странно то, что такой ошибки нет для других типов, таких как std::vector<int>, или для «эквивалентного» ограничения с использованием SFINAE.
Я знаю, почему vector<int>
работает, а vector<Foo>
нет. При определении концепции Barable
bar
является необъявленным идентификатором. Будучи зависимым именем, оно снова ищется в момент создания экземпляра, но только посредством поиска, зависящего от аргумента, а не посредством обычного поиска. Когда вы вызываете bar(std::vector<Foo>{})
, глобальное пространство имен связывается с типом аргумента, и bar(Foo)
находится. Когда вы вызываете bar(std::vector<int>{})
, глобальное пространство имен не связывается, и перегрузка bar
не обнаруживается. Поэтому vector<int>
однозначно нет Barable
.
Если вы введете struct Baz{};
и повторите попытку с std::vector<Baz>
, ошибка вернется. Дело не в том, что vector<Foo>
особенный — любой класс в глобальном пространстве имен позволит экземпляру Barable
найти bar(Foo)
посредством поиска, зависящего от аргументов, и запустить цикл.
bar(MyType{});
у меня тоже не работает. Возможно, я неправильно понял изменения, которые вы ожидали внести.
@IgorTandetnik Re bar(MyType{})
: вы внесли правильные изменения, я немного не понимаю, почему я вспомнил, что это работает - у меня, должно быть, были немного другие настройки, чем я думал при тестировании. Для меня имеет смысл то, что определенные типы не вызывают ошибку из-за отсутствия запуска ADL. Я был бы рад принять ответ, основанный на этом.
Отвечает ли это на ваш вопрос? Разрешение перегрузки функции в пункте require
При двухэтапном поиске имени имя bar
в определении Barable
ищется в двух точках — в точке определения с использованием обычного поиска и снова в точке создания экземпляра с использованием только поиска, зависящего от аргументов (ADL). Поскольку имя bar
не объявлено перед определением Barable
, первая фаза ничего не находит. Таким образом, концепция полностью полагается на поиск bar
с помощью ADL в момент создания экземпляра.
Когда ты позже сделаешь
SomeType arg;
bar(arg);
компилятор находит объявление bar(Foo)
посредством обычного поиска и должен определить, жизнеспособно ли оно: это может быть вызов bar(Foo{arg})
с использованием конструктора преобразования Foo
. Поэтому необходимо создать экземпляр этого преобразующего конструктора, что требует создания экземпляра концепции Barable<SomeType>
.
Это может пойти одним из двух путей. Если SomeType
не связан с глобальным пространством имен (как в случае, например, std::vector<int>
, или простого int
, или SomeNamespace::SomeType
), то ADL не находит никаких объявлений bar
; такого типа однозначно нет Barable
.
Если SomeType
действительно связан с глобальным пространством имен (как это было бы, например, с vector<Foo>
, или Baz
, или std::vector<Baz>
для некоторого типа Baz
, объявленного в глобальном пространстве имен), то bar(Foo)
найден. Затем в рамках создания экземпляра Barable<SomeType>
компилятору необходимо создать экземпляр bar(Foo{v})
для v
типа SomeType
— но помните, что мы уже находимся в процессе создания экземпляра именно этого. Отсюда ошибка, что понятие зависит от самого себя.
Учитывая комментарий пользователя 12002570, какой компилятор является правильным, согласно стандарту?
@LHLaurini Я бы поставил деньги на GCC и Clang. Но я должен признать, что не могу цитировать главу и стих сверху, и мне лень исследовать это.
Обратите внимание, что msvc компилирует этот код, а gcc и clang его отклоняют.