Перегруженный вызов неоднозначен: однопарная встроенная карта в качестве аргумента конструктора - PullRequest
0 голосов
/ 04 мая 2018

У меня есть класс - примерно похожий на тот, что ниже - - который принимает карту в качестве единственного аргумента своего единственного конструктора.

#include <iostream>
#include <map>

using namespace std;

class Dict {
    public:
    Dict (map<int, int> contents) {
        elements = contents;
    }

    int getElement (int i) {
        return elements[i];
    }

    map<int, int> elements;
};

int main() {
    Dict* test0 = new Dict({{1, 2}, {3, 4}});    /* Succeeds */
    Dict* test1 = new Dict({{1, 2}});            /* Fails */
}

Как уже упоминалось в комментариях выше, первый конструктор не выдает ошибку; это согласуется с ответами, такими как this . Неоднозначная ошибка вызова выглядит следующим образом:

main.cpp:43:36: error: call of overloaded 'Dict()' is ambiguous
    Dict* test1 = new Dict({{1, 2}});            /* Fails */
                                ^
main.cpp:16:5: note: candidate: Dict::Dict(std::map)
     Dict (map<int, int> contents) {
     ^
main.cpp:14:7: note: candidate: Dict::Dict(const Dict&)
 class Dict {
       ^
main.cpp:14:7: note: candidate: Dict::Dict(Dict&&)

Если ключи и значения на карте относятся к разным типам (например, если Dict() переводит карту целых чисел в логические значения, и я вызываю new Dict({{1, true}})), эта ошибка не возникает, и код работает должным образом .

Как этот единственный конструктор неоднозначен? Почему это неоднозначно, особенно в случае, когда существует одно отображение между двумя объектами одного типа? Есть ли очевидные обходные пути в vanilla C ++?

1 Ответ

0 голосов
/ 04 мая 2018

Это вызвано главным образом этим конструктором std::map:

template< class InputIterator >
map( InputIterator first, InputIterator last,
     const Compare& comp = Compare(),
     const Allocator& alloc = Allocator() );

Даже если аргументы не являются итераторами, этот конструктор включен, поэтому участвует в разрешении перегрузки. В результате

{1, 2}   -> std::map<int, int>
{{1, 2}} -> std::map<int, int>    

оба являются действительными преобразованиями, что означает

{{1, 2}} -> Dict
{{1, 2}} -> std::map<int, int> 

- оба допустимых преобразования. Следовательно, три конструктора Dict неоднозначны:

Dict(map<int, int>);
Dict(const Dict&);
Dict(Dict&&);

В случае new Dict({{1, true}}), InputIterator не может быть правильно выведен, поэтому двусмысленности больше нет.

Вы можете сделать Dict(map<int, int>); явным или использовать три пары скобок, предложенных Беном Фойгтом.


Почему работают три пары скоб?

Поскольку в этом случае для кандидата-конструктора копирования / перемещения пользовательские преобразования не допускаются. Это прямо указано в [over.best.ics] / 4 (я не имею отношения к другим частям):

Однако, если целью является

  • первый параметр конструктора или

  • ...

и конструктор или пользовательская функция преобразования являются кандидатами на

  • ... или

  • вторая фаза [over.match.list] , когда список инициализаторов содержит ровно один элемент, который сам является списком инициализаторов, а цель является первым параметром конструктора класса X, и преобразование в X или ссылку на cv X,

определяемые пользователем последовательности преобразования не учитываются.

...