2014-06-18 4 views
6

Имея следующий код, почему первое задание не вызывает шаблон operator= в Foo, но второй делает? Что здесь происходит? Есть ли сгенерированный компилятором один для первого присвоения, даже если пользовательский шаблон существует?с использованием оператора присваивания шаблона

#include <iostream> 

using namespace std; 

struct UberFoo { }; 

struct Foo : public UberFoo 
{ 
    template<typename T> void operator=(const T& v) { cout << "const T&" << endl; Set(v); } 
    template<typename T> void operator=(T& v) { cout << "T&" << endl; return Set(v); } 

    virtual void Set(const Foo&) { cout << "Foo::Set(const Foo&)" << endl; } 
    virtual void Set(const UberFoo&) { cout << "Foo::Set(const UberFoo&)" << endl; } 
}; 

struct Bar : public Foo 
{ 
    virtual void Set(const Foo&) { cout << "Bar::Set(const Foo&)" << endl; } 
    virtual void Set(const UberFoo&) { cout << "Bar::Set(const UberFoo&)" << endl; } 
}; 

int main() 
{ 
    Bar a, b; 

    Foo & pa = a; 
    const Foo& rb = b; 
    const UberFoo & urb = b; 

    cout << "First" << endl; 

    pa = rb; 

    cout << endl << "Second" << endl; 

    pa = urb; 

    return 0; 
} 
+0

Я знаю, что оператор присваивания должен возвращать Ref для себя (чтобы сделать цепочку возможной), но это не та точка – relaxxx

+0

Да, оператор назначения копирования всегда определен, если явно не удалено. Если вы его не определите, компилятор будет. –

ответ

5

Компилятор все еще производит в не-шаблонногоoperator= которой первое назначение связывается. Во втором назначении шаблонный operator= является лучшим кандидатом (потому что он не включает в себя литье), так что его выбирают.

Вы можете видеть, что при добавлении его в вашем коде:

Foo& operator=(const Foo&) = delete; 

Или заставить правильный шаблон вызова:

pa.operator=<Foo>(b); 

The standard говорит (курсив мой):

12,8 Копирование и перемещение объектов класса, §12.8/17, стр. 271:

Пользователя объявленной оператор присваивания копии Х :: = оператор не является членом статической функции не-шаблона класса X ровно с одним параметром типа X, X, & сопзИ Х &, летучее X & или Const летучий X &

§12.8/18, та же страница:

Если определение класса не явно объявить оператор присваивания копии, один объявлен неявно.

+0

и как выглядит сгенерированная подпись? когда 'T' выведено как' Foo', подпись такая же: 'void operator = (const Foo &)', поэтому это так же хорошо, как и сгенерированное компилятором. Почему 'T' не выводится как' Foo'? – relaxxx

+0

@relaxxx: он может быть «как хороший», как оператор присваивания копий, но он не является оператором присваивания копии по правилам, указанным в этом ответе. 'T' может быть выведено как' Foo', но нет необходимости в том, чтобы это происходило, поскольку уже существует соответствующая функция не-шаблона для вызова и имеет приоритет. –

+0

@relaxxx Как сказал LRIO, подпись такая же, но функция шаблонизирована. Не-шаблон лучше сочетается с шаблоном во время разрешения перегрузки, поэтому удаленный оператор выбирается как лучший кандидат. –

1

почему первое задание не вызывает оператор шаблона = в Foo, а второй делает? Что здесь происходит?

Помимо факта, объясненного ответом Уильяма на неявно сгенерированную функцию оператора присваивания копиям, в этом случае имеет место также разрешение перегрузки. Ниже перечислены кандидаты для первого оператора присваивания, после подстановки аргументов шаблона, как показано компилятором:

Foo& operator=(const Foo&);  // implicitly generated 
void operator=(const Foo&);  // template generated 

при прочих равных условиях, нешаблонная функции предпочтительнее шаблоны функций. В соответствии с C++ 11 (проект N3337), 13.3.3/1 (курсив мой)

Учитывая эти определения, жизнеспособного функция F1 определяется как лучше, чем другая функция жизнеспособной функции F2, если для все аргументы i, ICSi (F1) не хуже схемы преобразования, чем ICSi (F2), а затем

- для некоторого аргумента j ICSj (F1) является лучшей последовательностью преобразования, чем ICSj (F2), или, если не так,

- контекст является инициализацией по пользовательскому преобразованию (см. 8.5, 13.3.1.5 и 13.3.1.6) и стандартную последовательность преобразования из возвращаемого типа F1 в тип назначения (то есть тип инициализированного объекта ) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования от типа возврата от F2 до типа назначения. [...] или, если не то, что,

- F1 является функцией не-шаблон и F2 шаблон функции специализации, или, если не то, что,

- F1 и F2 шаблон функции специализации, а шаблон функции для F1 более специализирован , чем шаблон для F2 в соответствии с правилами частичного упорядочения, описанными в 14.5.6.2.

Это объясняет, почему выбрана перегрузка без шаблонов. Вы можете проверить то же самое с небольшим упражнением:

void print(int, int) { std::cout << "int"; } 
template <typename T> void print(T, T) { std::cout << "T"; } 
print(1, 2); // prints int 
print<>(1, 2); // prints T 

Что касается второго задания, он видит

Foo& operator=(const Foo&);  // implicitly generated 
void operator=(const UberFoo&); // template generated 

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

Смежные вопросы