Шаблонные функции-члены – чудесная вещь, но они не отменяют основных правил языка. В правиле 5 объясняется, что две из четырех функций-членов, которые компиляторы могут генерировать автоматически, – это конструктор копирования и оператор присваивания. В классе tr1::shared_ptr объявлен обобщенный конструктор копирования, и ясно, что в случае совпадения типов T и Y конкретизация обобщенного конструктора копирования может быть сведена к созданию «обычного» конструктора копирования. Поэтому возникает вопрос, что будет делать компилятор в случае, когда один объект tr1::shared_ptr конструируется из другого объекта того же типа: генерировать обычный конструктор копирования для tr1::shared_ptr или конкретизировать обобщенный конструктор копирования из шаблона?
Как я сказал, шаблонные члены не отменяют основных правил языка, а из этих правил следует, что если конструктор копирования нужен, а вы не объявляете его, то он будет сгенерирован автоматически. Объявление в классе обобщенного конструктора копирования (шаблонного члена) не предотвращает генерацию компилятором обычного конструктора копирования. Поэтому если вы хотите полностью контролировать все аспекты конструирования путем копирования, то должны объявить как обобщенный конструктор копирования, так и обычный. То же касается присваивания. Приведем фрагмент определения класса tr1::shared_ptr, который иллюстрирует это положение:
template
public:
shared_ptr(shared_ptr const& r); // конструктор копирования
template
shared_ptr(shared_ptr
shared_ptr& operator=(shared_ptr const& r); // оператор присваивания
template
shared_ptr& operator=(shared_ptr
...
};
• Используйте шаблонные функции-члены для генерации функций, принимающих все совместимые типы.
• Если вы объявляете шаблоны обобщенных конструкторов копирования или обобщенного оператора присваивания, то по-прежнему должны объявить обычный конструктор копирования и оператор присваивания.
Правило 46: Определяйте внутри шаблонов функции, не являющиеся членами, когда желательны преобразования типа
В правиле 24 объясняется, почему только к свободным функциям применяются неявные преобразования типов всех аргументов. В качестве примера была приведена функция operator* для класса Rational. Прежде чем продолжить чтение, рекомендую вам освежить этот пример в памяти, потому что сейчас мы вернемся к этой теме, рассмотрев безобидные, на первый взгляд, модификации примера из правила 24. Отличие только в том, что и класс Rational, и operator* в нем сделаны шаблонами:
template
class Rational {
public:
Rational(const T& numerator = 0, // см. в правиле 20 – почему
const T& denominator = 1); // параметр передается по ссылке
const T numerator() const; // см. в правиле 28 – почему
const T denominator() const; // результат возвращается по
... // значению, а в правиле 3 –
// почему они константны
};
template
const Rational
const Rational
{...}
Как и в правиле 24, мы собираемся поддерживать смешанную арифметику, поэтому хотелось бы, чтобы приведенный ниже код компилировался. Мы не ожидаем подвохов, потому что аналогичный код в правиле 24 работал. Единственное отличие в том, что класс Rational и функция-член operator* теперь шаблоны:
Raional
// но Rational – теперь шаблон
Ratinal