2016-05-13 13 views
10

Я делал тонкий производный класс с конструктором пересылки. (Медведь со мной, я должен использовать GCC 4.7.2, который не имеет унаследованных конструкторов).Почему этот оператор = вызов неоднозначный?

С первой попытки я забыл добавить ключевое слово explicit и получил ошибку. Может ли кто-нибудь объяснить, почему именно эта ошибка возникает? Мне трудно понять последовательность событий.

#include <memory> 

template<typename T> 
struct shared_ptr : std::shared_ptr<T> 
{ 
    template<typename...Args> 
    /*explicit*/ shared_ptr(Args &&... args) 
    : std::shared_ptr<T>(std::forward<Args>(args)...) 
    {} 
}; 

struct A {}; 

struct ConvertsToPtr 
{ 
    shared_ptr<A> ptr = shared_ptr<A>(new A()); 
    operator shared_ptr<A> const &() const { return ptr; } 
}; 

int main() 
{ 
    shared_ptr<A> ptr; 
    ptr = ConvertsToPtr(); // error here 
    return 0; 
} 

Ошибка:

test.cpp: In function ‘int main()’: 
test.cpp:28:23: error: ambiguous overload for ‘operator=’ in ‘ptr = ConvertsToPtr()’ 
test.cpp:28:23: note: candidates are: 
test.cpp:9:8: note: shared_ptr<A>& shared_ptr<A>::operator=(const shared_ptr<A>&) 
test.cpp:9:8: note: shared_ptr<A>& shared_ptr<A>::operator=(shared_ptr<A>&&) 

ответ

7

Это также случай с g++ 4.8.4 со следующим:
g++ -g -pedantic --std=c++11 -o test main.cpp
Установки VS2015 все по умолчанию.

Проблема заключается в том, что компилятор пытается преобразовать временный объект, возвращенный ConvertsToPtr() в объект shared_ptr. Когда компилятор используется с ключевым словом explicit, это преобразование никогда не происходит с помощью конструктора. Однако при рассмотрении с помощью gdb появляется, что вместо этого используется функция преобразования shared_ptr<A> const &() для соответствия соответствующему типу. Затем это преобразование возвращает const shared_ptr &, который не имеет двусмысленности при вызове оператора присваивания (это также соответствует выводам wojciech Frohmberg).

Однако, если explicit опущен, возвращается объект shared_ptr. это может быть сопоставлено либо с rvalue версией оператора присваивания, либо с версией const lvalue.

Согласно N4296, Table-11, мы имеем после строительства с conversion constructor объект rvalue of shared_ptr. Однако разрешение перегрузки обнаруживает два совпадения, оба из которых находятся под Exact Match (версия rvalue равна Identity matching, а другая - Qualification matching).

Я также проверял также на VS2015 и, как указано в комментариях, он работает. Но, используя некоторую отладку cout, можно видеть, что назначение const lvalue имеет приоритет над значением rvalue const lvalue refrence version.

EDIT: Я посмотрел немного глубже в стандарте и добавил модификацию. удаленный текст относительно результатов VS2015 был неправильным, потому что я не определял оба назначения. Когда оба назначения были объявлены, он предпочитает rvalue.

Я предполагаю, что VS-компилятор отличает Identity от Qualification, соответствующих ранжированию. Однако, как я заключаю, это компилятор VS не работает. компиляторы g++ подчиняются данному стандарту. Однако, поскольку GCC 5.0 работает как Visual Studio, возможность ошибки компилятора тонкая, поэтому я был бы рад увидеть других экспертов.

EDIT: В 13.3.3.2 один из вытяжных выключателей, после лучшего ранжирования я писал об этом, это:

— S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

Существует пример прилагается, показывающими, что данный Rvalue (не Rvalue ссылки) должен совпадать с const int && над const int &. Поэтому, я думаю, можно с уверенностью предположить, что это относится к нашему делу, даже если у нас есть тип &&, а не тип const &&. Я думаю, после всего этого GCC 4.7.4.8 в любом случае глючит.

+0

Конечно, rvalues ​​привязывают к константам lvalue. Попробуйте вызвать 'void f (float const &)' с '0f'. – AndyJost

+1

Итак, после создания 'ConvertsToPtr' компилятор просматривает' shared_ptr :: operator = 'и переходит с разрешением перегрузки. Кандидат, принимающий 'shared_ptr const &', получает успех из-за оператора преобразования. Без «явного», кандидат, принимающий 'shared_ptr &&', также успешно выполняется с помощью преобразования на основе конструктора. Ни одно преобразование не лучше, поэтому вызов неоднозначен. Имеет смысл. – AndyJost

+0

@ AndyJost: кратчайший путь преобразования явно лучше, поскольку C++ не допускает двух определенных пользователем преобразований в последовательности, поэтому это ошибка компилятора. –