Почему компилятор проверяет эту концепцию и выдает ошибку?

Компиляция следующего кода приводит к: 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>). Для любого другого типа это работает именно так.

Примечания:

  • Я проверил магистральные версии gcc и clang.
  • маркировка Foo(T) явная или предоставление конструктора Foo(std::vector<Foo>) решает проблему
  • переход на другой тип (например, int или std::vector<int>) решает проблему
  • Демо: https://godbolt.org/z/6qxTrzMd8
  • Впервые я наткнулся на это, когда играл с кодом Шона Парента «Наследование — это базовый класс всего зла»

Обратите внимание, что msvc компилирует этот код, а gcc и clang его отклоняют.

user12002570 28.04.2024 11:48

Кажется, компилятор пытается выполнить разрешение перегрузки между bar(std::vector<Foo>) и bar(Foo). Последний теоретически можно вызвать через bar(Foo{v}), используя конструктор преобразования Foo. Для этого компилятору необходимо определить, является ли Foo{v} жизнеспособным, а это зависит от того, является ли vector<Foo>Barable, а это, в свою очередь, требует проверки bar(v) на наличие v типа std::vector<Foo>, что завершает круг.

Igor Tandetnik 28.04.2024 15:53

@ИгорьТандетник Согласен. Странно то, что такой ошибки нет для других типов, таких как std::vector<int>, или для «эквивалентного» ограничения с использованием SFINAE.

David Ochsner 28.04.2024 17:24

Я знаю, почему vector<int> работает, а vector<Foo> нет. При определении концепции Barablebar является необъявленным идентификатором. Будучи зависимым именем, оно снова ищется в момент создания экземпляра, но только посредством поиска, зависящего от аргумента, а не посредством обычного поиска. Когда вы вызываете bar(std::vector<Foo>{}), глобальное пространство имен связывается с типом аргумента, и bar(Foo) находится. Когда вы вызываете bar(std::vector<int>{}), глобальное пространство имен не связывается, и перегрузка bar не обнаруживается. Поэтому vector<int> однозначно нет Barable.

Igor Tandetnik 28.04.2024 19:07

Если вы введете struct Baz{}; и повторите попытку с std::vector<Baz>, ошибка вернется. Дело не в том, что vector<Foo> особенный — любой класс в глобальном пространстве имен позволит экземпляру Barable найти bar(Foo) посредством поиска, зависящего от аргументов, и запустить цикл.

Igor Tandetnik 28.04.2024 19:11
bar(MyType{});у меня тоже не работает. Возможно, я неправильно понял изменения, которые вы ожидали внести.
Igor Tandetnik 28.04.2024 19:14

@IgorTandetnik Re bar(MyType{}): вы внесли правильные изменения, я немного не понимаю, почему я вспомнил, что это работает - у меня, должно быть, были немного другие настройки, чем я думал при тестировании. Для меня имеет смысл то, что определенные типы не вызывают ошибку из-за отсутствия запуска ADL. Я был бы рад принять ответ, основанный на этом.

David Ochsner 28.04.2024 21:29

Отвечает ли это на ваш вопрос? Разрешение перегрузки функции в пункте require

303 04.05.2024 11:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
8
129
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

При двухэтапном поиске имени имя 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 28.04.2024 22:13

@LHLaurini Я бы поставил деньги на GCC и Clang. Но я должен признать, что не могу цитировать главу и стих сверху, и мне лень исследовать это.

Igor Tandetnik 28.04.2024 22:17

Другие вопросы по теме