вызов перегруженного ‹списка инициализаторов в фигурных скобках› неоднозначен, как с этим бороться?

Я действительно не понимаю этого, я думал, что компилятор сначала выполняет то, что в фигурных скобках, а затем отдает результат наиболее подходящей функции. Здесь похоже, что он дает функции список инициализаторов для работы с ней...

#include <string>
#include <vector>
using namespace std;

void func(vector<string> v) { }

void func(vector<wstring> v) { }

int main() {
  func({"apple", "banana"});
}

ошибка:

<stdin>: In function 'int main()':
<stdin>:11:27: error: call of overloaded 'func(<brace-enclosed initializer list>)' is ambiguous
<stdin>:11:27: note: candidates are:
<stdin>:6:6: note: void func(std::vector<std::basic_string<char> >)
<stdin>:8:6: note: void func(std::vector<std::basic_string<wchar_t> >)

Почему моя перегрузка func(vector<string> v) не вызывается и можно ли это сделать?


person rsk82    schedule 29.01.2013    source источник


Ответы (1)


Этот был тонким.

std::vector имеет конструктор, принимающий два итератора диапазона. Это конструктор template (определенный в 23.6.6.2 стандарта C++11):

template<typename InputIterator>
vector(InputIterator first, InputIterator last, 
const allocator_type& a = allocator_type());

Теперь конструктор std::vector<wstring>, принимающий initializer_list, не соответствует неявному преобразованию в вашем вызове функции (const char* и string - разные типы); но тот, что выше, который, конечно, включен как в std::vector<string>, так и в std::vector<wstring>, является потенциально идеальным совпадением, потому что InputIterator можно вывести как const char*. Если не используется какой-либо метод SFINAE для проверки того, действительно ли выведенный аргумент шаблона удовлетворяет концепции InputIterator для базового типа вектора, что не является нашим случаем, этот конструктор является жизнеспособным.

Но опять же, и std::vector<string>, и std::vector<wstring> имеют жизнеспособный конструктор, который реализует преобразование из списка инициализаторов в фигурных скобках: отсюда и неоднозначность.

Таким образом, проблема заключается в том, что, хотя "apple" и "banana" на самом деле не являются итераторами (*), в конечном итоге они рассматриваются как таковые. Добавление одного аргумента "joe" к вызову функции устраняет проблему, устраняя неоднозначность вызова, поскольку это заставляет компилятор исключать конструкторы на основе диапазона и выбирать единственное приемлемое преобразование (initializer_list<wstring> не< /em> жизнеспособно, потому что const char* нельзя преобразовать в wstring).


*На самом деле они являются указателями на const char, поэтому их можно даже рассматривать как константные итераторы для символов, но определенно не для строк, как хочет думать наш конструктор шаблонов.

person Andy Prowl    schedule 29.01.2013
comment
@OmnipotentEntity: Спасибо :-) - person Andy Prowl; 29.01.2013
comment
Теперь я еще больше ненавижу так называемую униформную инициализацию, она мешает моей любимой фиче, std::initializer_list. - person Benjamin Lindley; 29.01.2013
comment
@BenjaminLindley: Действительно, это досадное вмешательство. Мне все еще интересно, означает ли использование имени InputIterator для параметра шаблона в Стандарте, что компилятор должен проверить, удовлетворяет ли параметр шаблона концепции InputIterator. Я думаю, что это не так, но я не уверен на 100%. - person Andy Prowl; 29.01.2013
comment
@Andy Andy Но это действительно соответствует концепции InputIterator (и не только), не так ли? Строковые литералы неявно преобразуются в char const *, а это RandomAccessIterator. - person Praetorian; 29.01.2013
comment
@Praetorian: Вы правы, это так, но как постоянный итератор chars, а не strings или wstrings. - person Andy Prowl; 29.01.2013
comment
он принимает другой ctor, потому что ctor списка инициализации не только не является идеальным совпадением, но и вообще не соответствует :) он не будет принимать c-строки, а только wc-строки, как вы укажете позже в своем ответе. к сожалению, даже не сделав ctor explicit проблему не исправит :) Разрешение перегрузки все равно будет считать и вызов все равно неоднозначный. - person Johannes Schaub - litb; 31.01.2013
comment
@JohannesSchaub-litb: Для vector<wstring> да, вы правы: это вообще не совпадение. Но для vector<string> это совпадение, но не идеальное: требует конвертации, а шаблонная версия не требует конвертации и поэтому предпочтительнее. Но оба класса имеют шаблонную версию, что и приводит к неоднозначности. Или я ошибаюсь? - person Andy Prowl; 31.01.2013
comment
@AndyProwl из вашего описания мне кажется, что этот конструктор лучше подходит, чем конструктор списка инициализаторов, поэтому он предпочтительнее. Но даже если конструктор шаблона будет лучше соответствовать, если конструкторы будут сравниваться друг с другом, компилятор все равно будет использовать конструктор списка инициализаторов. Потому что компилятор сначала только смотрит на конструкторы списка инициализаторов. И только если они совсем не совпадают, то он рассматривает дополнительно все остальные конструкторы, на этот раз принимая в качестве аргументов элементы списка инициализаторов по отдельности, а не весь список. - person Johannes Schaub - litb; 31.01.2013
comment
@JohannesSchaub-litb: Вам удалось вернуть мою уверенность в замешательство, но я думаю, именно это приводит к реальным знаниям :-) Если компилятор сначала смотрит на конструкторы списка инициализаторов, почему он не выбирает vector<string>(initializer-list), который является жизнеспособным? - person Andy Prowl; 31.01.2013
comment
@AndyProwl, он действительно выбирает это - person Johannes Schaub - litb; 31.01.2013
comment
@JohannesSchaub-litb: [почесывая затылок] Так почему такая двусмысленность? - person Andy Prowl; 31.01.2013
comment
@AndyProwl, потому что для vector<wstring> он выбирает конструктор шаблона, а затем оба func совпадают, и ни один из них не лучше другого. Если бы один из func имел свой параметр типа std::initializer_list<X>, а другой — нет, этот func был бы предпочтительнее. Неважно, что в контексте разрешения вложенной перегрузки один параметр функций использует конструктор списка инициализаторов для своей инициализации. Важен сам тип параметра, который в обоих случаях равен vector<X>. - person Johannes Schaub - litb; 31.01.2013
comment
@JohannesSchaub-litb: Кажется, я начинаю понимать, спасибо за объяснение. Таким образом, при сравнении последовательностей преобразования из initializer_list<const char*> в std::vector<string> и std::vector<wstring> обе последовательности одинаково хороши, независимо от конструкторов, вызываемых для реализации преобразований. Я правильно понял? (скрещенные пальцы) - person Andy Prowl; 31.01.2013
comment
initializer_list<const char*> нет. { "apple", "banana" } — это список инициализаторов в фигурных скобках. Его можно преобразовать в struct A { char const*a, *b; }; так же, как и в char const *x[2];. А при инициализации std::vector<string> он преобразуется (в разрешении вложенной перегрузки конструкторов списка инициализаторов) в std::initializer_list<string>. - person Johannes Schaub - litb; 31.01.2013
comment
@AndyProwl, но я думаю, вы поняли это прямо сейчас, за исключением путаницы, связанной с введением этого initializer_list<const char*> :) Две определяемые пользователем последовательности преобразования можно сравнивать только в том случае, если обе последовательности используют один и тот же конструктор или функцию преобразования (C++ 11 добавил некоторые другие случаи, связанные с совокупная инициализация, которая, однако, не имеет значения в данном случае). В этом случае конструкторы являются членами разных классов, так что это вряд ли сработает :) - person Johannes Schaub - litb; 31.01.2013
comment
@JohannesSchaub-litb: Спасибо, это отличный урок для меня. Надеюсь, я больше не совершу эту логическую ошибку. - person Andy Prowl; 31.01.2013
comment
Например, в struct A { operator int(); } void f(int); void f(long);, если вы вызываете его с помощью f(A());, то две определяемые пользователем последовательности преобразования A->int и A->long используют одну и ту же функцию преобразования. Их можно сравнивать (сравнивается последовательность их преобразования из возвращаемого типа функции преобразования в целевой тип последовательности). И для первого лучше, чем для второго (тождество против интегрального преобразования). Следовательно, для этого побеждает первый f. - person Johannes Schaub - litb; 31.01.2013
comment
@JohannesSchaub-litb: понятно. Итак, здесь у нас есть две последовательности преобразования, одна из A в int, а другая из A в long, но первая предпочтительнее другой, потому что она требует только одного определяемого пользователем преобразования, а вторая требует определяемого пользователем преобразования. и встроенное преобразование. В то время как в примере ОП у нас было две одинаково хорошие последовательности преобразования, состоящие из одного определяемого пользователем преобразования, и не имеет значения, достигаются ли эти преобразования с помощью конструктора, принимающего initializer_list, или с помощью конструкции шаблона. Я наконец понял? - person Andy Prowl; 31.01.2013
comment
@AndyProwl, да, это так :) - person Johannes Schaub - litb; 02.02.2013