Я пытаюсь учесть функцию с помощью объявления 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
.
Я изо всех сил пытаюсь понять причину, по которой это не работает, и найти решение, позволяющее включить этот шаблон.
Что касается некоторых ошибок, которые вы получаете, если вы их прочтете, они скажут, что компилятор пытается использовать std::equal
. Это происходит из-за ADL (аргументно-зависимый поиск), который происходит потому, что один из аргументов (объект списка инициализаторов) является частью пространства имен std
.
Поскольку вы передаете std::initializer_list
, поиск, зависящий от аргумента, выбирает std::equal
в качестве equal
. Самое простое решение — вызвать n::equal
, а не просить компилятор выбрать, какую функцию equal
использовать.
Обычная ошибка - перестаньте читать примечания к сообщениям об ошибках, в них часто содержатся ответы. Здесь <source>:11:5: note: in instantiation of function template specialization 'std::equal<n::S, std::initializer_list<int>>' requested here
вас уведомят, что n::equal
не используется.
@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 }; };
Я пронумерую эти звонки:
equal(S{},S{}); // #1
equal(S{},S{},{1}); // #2
equal(S{},S{},std::initializer_list<int>{1, 2}); // #3
Итак, в #1
и #2
единственным жизнеспособным кандидатом является n::equal
, поэтому его вызывают, и все работает.
Но в #3
std::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.
На самом деле,
{1}
будет преобразовано вstd::initializer_list<int>
компилятором. Который затем будет использовать его для создания временного объектаstd::vector<int>
, который передается функции. Практически нет вариантов использования для явного создания объектаstd::initializer_list<T>
.