Если точка настройки должна действовать и для встроенных типов, используйте варианты 2 и 3.
Варианты 1 и 2 следует предпочесть для тех общих операций, которые являются предоставляемыми типом сервисами. Для принятия данного решения попробуйте ответить на следующие вопросы: могут ли другие библиотеки шаблонов использовать данную возможность? является ли рассматриваемая семантика приемлемой для данного имени в общем случае? Если вы положительно ответили на эти вопросы, то, вероятно, вам действительно следует предпочесть один из этих вариантов.
Вариант 3 лучше использовать для менее общих операций, смысл которых может варьироваться. В таком случае в другом пространстве имен без каких-либо коллизий вы сможете придать тому же имени иной смысл.
Шаблон, в котором имеется несколько точек настройки, для каждой из них может выбрать свою стратегию, в наибольшей мере приемлемую в данном месте. Главное, что вы должны осознанно, с пониманием выбирать стратегию для каждой точки настройки, документировать требования к настройке (включая ожидаемые постусловия и семантику ошибок) и корректно реализовать выбранную вами стратегию.
Для того чтобы избежать непреднамеренных точек настройки, следует придерживаться следующих правил.
• Размещайте все используемые вашим шаблоном вспомогательные функции в их собственном вложенном пространстве имен, и вызывайте их посредством полностью квалифицированных имен для запрета ADL. Если вы вызываете вашу вспомогательную функцию и передаете ей объект типа параметра шаблона, и этот вызов не должен быть точкой настройки (т.е. вы всегда намерены вызывать вашу вспомогательную функцию, а не некоторую иную), то лучше поместить эту вспомогательную функцию во вложенное пространство имен и явно запретить ADL, полностью квалифицировав имя вызываемой функции или взяв его в скобки:
template
void Samplе4(T t) {
S4Helpers::bar(t); // Запрет ADL: bar не является
// точкой настройки
(bar)(t); // Альтернативный способ
}
• Избегайте зависимости от зависимых имен. Говоря неформально, зависимое имя — это имя, которое каким-то образом упоминает параметр шаблона. Многие компиляторы не поддерживают "двухфазный поиск" для зависимых имен из стандарта С++, а это означает, что код шаблона, использующий зависимые имена, будет вести себя по-разному на разных компиляторах, если только не принять меры для полной определенности при использовании зависимых имен. В частности, особого внимания требует наличие зависимых базовых классов, когда шаблон класса наследуется от одного из параметров этого шаблона (например, T
в случае templateclass С:T{};
) или от типа, который построен с использованием одного из параметров шаблона (например, X
в случае templateclass C:X{};
).
Коротко говоря, при обращении к любому члену зависимого базового класса необходимо всегда явно квалифицировать имя с использованием имени базового класса или при помощи this->
. Этот способ можно рассматривать просто как некую магию, которая заставляет все компиляторы делать именно то, что вы от них хотите.
template
class С : X {
typename X::SomeType s; // Использование вложенного
// типа (или синонима
// typedef) из базового
// класса
public:
void f() {
X::baz(); // вызов функции-члена
// базового класса
this->baz(); // Альтернативный способ
}
};
Стандартная библиотека С++ в основном отдает предпочтение варианту 2 (например, ostream_iterator
ищет оператор operator<<
, a accumulate
ищет оператор operator+
в пространстве имен вашего типа). В некоторых местах стандартная библиотека использует также вариант 3 (например, iterator_traits
, char_traits
) в основном потому, что эти классы свойств должны быть специализируемы для встроенных типов.
Заметим, что, к сожалению, стандартная библиотека С++ не всегда четко определяет точки настройки некоторых алгоритмов. Например, она ясно говорит о том, что трехпараметрическая версия accumulate
должна вызывать пользовательский оператор operator+
с использованием второго варианта. Однако она не говорит, должен ли алгоритм sort
вызывать пользовательскую функцию swap
(обеспечивая таким образом преднамеренную точку настройки с использованием варианта 2), может ли он использовать пользовательскую функцию swap
, и вызывает ли он функцию swap
вообще; на сегодняшний день некоторые реализации sort
используют пользовательскую функцию swap
, в то время как другие реализации этого не делают. Важность рассматриваемой рекомендации была осознана совсем недавно, и сейчас комитет по стандартизации исправляет ситуацию, устраняя такие нечеткости из стандарта. Не повторяйте такие ошибки. (См. также рекомендацию 66.)
Читать дальше