Почему функция с параметром std::initializer_list не найдена, несмотря на попытку учесть ее с помощью объявления «using»

Я пытаюсь учесть функцию с помощью объявления using:

namespace n {
    struct S {};
    bool equal(S a, S b, std::vector<int> = {1,2}) { return false; }
}

int main() {
    using ::n::equal;
    using ::n::S;
    /// ...
}

Это работает хорошо, пока в качестве третьего аргумента не передается std::initializer_list:

equal(S{},S{});
equal(S{},S{},{1});

Но это ломается, когда std::initializer_list передается в качестве третьего аргумента:

equal(S{},S{},std::initializer_list<int> {1, 2});

Учитывается только std::equal.

Я изо всех сил пытаюсь понять причину, по которой это не работает, и найти решение, позволяющее включить этот шаблон.

живая демонстрация

На самом деле, {1} будет преобразовано в std::initializer_list<int> компилятором. Который затем будет использовать его для создания временного объекта std::vector<int>, который передается функции. Практически нет вариантов использования для явного создания объекта std::initializer_list<T>.

Some programmer dude 05.08.2024 18:15

Что касается некоторых ошибок, которые вы получаете, если вы их прочтете, они скажут, что компилятор пытается использовать std::equal. Это происходит из-за ADL (аргументно-зависимый поиск), который происходит потому, что один из аргументов (объект списка инициализаторов) является частью пространства имен std.

Some programmer dude 05.08.2024 18:19

Поскольку вы передаете std::initializer_list, поиск, зависящий от аргумента, выбирает std::equal в качестве equal. Самое простое решение — вызвать n::equal, а не просить компилятор выбрать, какую функцию equal использовать.

Drew Dormann 05.08.2024 18:19

Обычная ошибка - перестаньте читать примечания к сообщениям об ошибках, в них часто содержатся ответы. Здесь <source>:11:5: note: in instantiation of function template specialization 'std::equal<n::S, std::initializer_list<int>>' requested here вас уведомят, что n::equal не используется.

3CxEZiVlQ 05.08.2024 18:26

@Someprogrammerdude Практически нет вариантов использования для явного создания объекта std::initializer_list<T>. Я не уверен, что мой вариант использования является разумным. Я просто играю, пытаясь создать флаг типа enum, который можно было бы использовать с любым набором, например type, и инициализировать наборы, например struct MyEnum { static constexpr int a = 1; static constexpr int b = 2; static constexpr auto ab = { a, b }; };

ridilculous 05.08.2024 18:29
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
89
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Я пронумерую эти звонки:

equal(S{},S{});                                    // #1
equal(S{},S{},{1});                                // #2
equal(S{},S{},std::initializer_list<int>{1, 2});   // #3

Итак, в #1 и #2 единственным жизнеспособным кандидатом является n::equal, поэтому его вызывают, и все работает.

Но в #3std::equal внезапно становится кандидатом — его можно найти с помощью аргументо-зависимого поиска (ADL) из-за явного аргумента std::initializer_list. И есть перегрузка std::equal, которая выглядит так:

  template<class InputIterator1, class InputIterator2>
    constexpr bool equal(InputIterator1 first1, InputIterator1 last1,
                         InputIterator2 first2);

Обратите внимание, что хотя параметры шаблона называются InputIterator1 и InputIterator2 и эти типы действительно должны быть входными итераторами, сам алгоритм не имеет ограничений, поэтому он считается жизнеспособным кандидатом.

А между n::equal и std::equal последнее соответствует лучше — все аргументы точно совпадают, тогда как для n::equal требуется явное преобразование из std::initializer_list<int> в std::vector<int>. Таким образом, выбирается std::equal, который не будет компилироваться, поскольку ни один из этих типов не является итераторами.


Самые простые решения — просто не использовать ADL или не передавать явный std::initializer_list<int> и просто передавать std::vector<int> вручную.

В качестве альтернативы вы можете добавить дополнительную перегрузку n::equal для обработки этого случая:

bool equal(S a, S b, std::vector<int> = {1,2});
bool equal(S a, S b, std::initializer_list<int> xs) {
    return equal(a, b, std::vector<int>(xs));
}

Напротив, std::ranges::equal ограничено и не может быть найдено ADL.

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