Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация
typename T::value_type x; // Еще один пример: создание
} // точки настройки для поиска
// типа (обычно создается посредством typedef)
Для реализации первого варианта автор Sample1 должен выполнить следующие действия.
• Вызвать функцию как член. Просто используйте естественный синтаксис вызова функции-члена.
• Документировать точку настройки. Тип должен обеспечить доступную функцию-член foo, которая может быть вызвана с данными аргументами (в данном случае — без аргументов).
Второй вариант представляет собой использование метода "неявного интерфейса", но с функциями, не являющимися членами, поиск которых выполняется с использованием ADL[3](т.е. ожидается, что данная функция находится в пространстве имен типа, для которого выполняется инстанцирование шаблона). Именно эта ситуация и явилась основной побудительной причиной для введения ADL (см. рекомендацию 57). Ваш шаблон рассчитывает на то, что для используемого типа имеется подходящая функция с заданным именем:
// Вариант 2: Создание точки настройки путем требования от
// типа T "fоо-совместимости", т.е. наличия функции, не
// являющейся членом с данным именем, сигнатурой и
// семантикой, поиск которой выполняется посредством ADL.
// (Это единственный вариант, при котором не требуется поиск
// самого типа T.)
template<typename T>
void Samplе2(T t) {
foo(t); // foo - точка настройки
cout << t; // Еще один пример - operator<< с записью в
} // виде оператора представляет собой такую же
// точку настройки
Для реализации варианта 2 автор Samplе2 должен выполнить следующие действия.
• Вызвать функцию с использованием неквалифицированного имени (включая использование естественного синтаксиса в случае операторов) и убедиться, что шаблон не имеет функции-члена с тем же именем. В случае шаблонов очень важно, чтобы вызов функции был не квалифицированным (например, не следует писать SomeNamespace::foo(t)) и чтобы у шаблона не было функции-члена с тем же именем, поскольку в обоих этих случаях поиск, зависящий от аргумента, выполняться не будет, что предотвратит поиск имени в пространстве имен, в котором находится тип T.
• Документировать точку настройки. Тип должен обеспечить наличие функции, не являющейся членом, которая может быть вызвана с данными аргументами.
Варианты 1 и 2 имеют одинаковые преимущества и применимость: пользователь может один раз написать соответствующую функцию настройки для своего типа и разместить ее там, где ее смогут найти и шаблоны других библиотек. Тем самым пользователь избегает необходимости писать множество мелких адаптеров для каждой библиотеки отдельно. Недостаток же заключается в том, что соответствующая семантика должна быть достаточно широко применима и иметь смысл для всех такого рода потенциальных применений (заметим, что в частности в эту категорию попадают операторы, что является еще одной причиной для рекомендации 26).
Третий вариант заключается в использовании специализации, когда ваш шаблон полагается на то, что пользовательский тип специализирует (при необходимости) некоторый иной предоставленный вами шаблон класса.
// Вариант 3: Создание точки настройки путем требования от
// типа T "foo-совместимости" путем специализации шаблона
// SampleTraits<> с предоставлением (обычно статической)
// функции с данным именем, сигнатурой и семантикой.
template<typename T>
void Samplе3(T t) {
S3Traits<T>::foo(t); // S3Traits<>::foo -
// точка настройки
typename S3Traits<T>::value_type x; // Другой пример -
} // точка настройки для поиска типа (обычно
// создается посредством typedef)
В этом варианте пользователь пишет адаптер, который гарантирует изолированность кода настройки для данной библиотеки в пределах этой библиотеки. Соответствующий недостаток заключается в том, что это может оказаться слишком громоздким решением; если несколько библиотек шаблонов требуют одну и ту же общую функциональность, пользователь должен будет писать несколько адаптеров, по одному для каждой библиотеки.
Для реализации этой версии автор Samplе3 должен выполнить следующие действия.
• Предоставить шаблон класса по умолчанию в собственном пространстве имен шаблона. Не используйте шаблоны функций, которые нельзя частично специализировать и которые приводят к перегрузкам и зависимостям от порядка (см. также рекомендацию 66).
• Документировать точку настройки. Пользователь должен специализировать S3Traits для своего собственного типа в пространстве имен библиотеки шаблонов, и документировать все члены S3Traits (например, foo) и их семантику.
При использовании любого из перечисленных вариантов следует также четко документировать семантику, требуемую от foo, в особенности все существенные действия (постусловия), которые должна гарантировать эта функция, и семантику сбоев (что именно происходит при сбое и каким образом должно осуществляться оповещение об ошибках).
Если точка настройки должна действовать и для встроенных типов, используйте варианты 2 и 3.
Варианты 1 и 2 следует предпочесть для тех общих операций, которые являются предоставляемыми типом сервисами. Для принятия данного решения попробуйте ответить на следующие вопросы: могут ли другие библиотеки шаблонов использовать данную возможность? является ли рассматриваемая семантика приемлемой для данного имени в общем случае? Если вы положительно ответили на эти вопросы, то, вероятно, вам действительно следует предпочесть один из этих вариантов.
Вариант 3 лучше использовать для менее общих операций, смысл которых может варьироваться. В таком случае в другом пространстве имен без каких-либо коллизий вы сможете придать тому же имени иной смысл.
Шаблон, в котором имеется несколько точек настройки, для каждой из них может выбрать свою стратегию, в наибольшей мере приемлемую в данном месте. Главное, что вы должны осознанно, с пониманием выбирать стратегию для каждой точки настройки, документировать требования к настройке (включая ожидаемые постусловия и семантику ошибок) и корректно реализовать выбранную вами стратегию.
Для того чтобы избежать непреднамеренных точек настройки, следует придерживаться следующих правил.
• Размещайте все используемые вашим шаблоном вспомогательные функции в их собственном вложенном пространстве имен, и вызывайте их посредством полностью квалифицированных имен для запрета ADL. Если вы вызываете вашу вспомогательную функцию и передаете ей объект типа параметра шаблона, и этот вызов не должен быть точкой настройки (т.е. вы всегда намерены вызывать вашу вспомогательную функцию, а не некоторую иную), то лучше поместить эту вспомогательную функцию во вложенное пространство имен и явно запретить ADL, полностью квалифицировав имя вызываемой функции или взяв его в скобки:
template<typename T>
void Samplе4(T t) {
S4Helpers::bar(t); // Запрет ADL: bar не является
// точкой настройки
(bar)(t); // Альтернативный способ
}
• Избегайте зависимости от зависимых имен. Говоря неформально, зависимое имя — это имя, которое каким-то образом упоминает параметр шаблона. Многие компиляторы не поддерживают "двухфазный поиск" для зависимых имен из стандарта С++, а это означает, что код шаблона, использующий зависимые имена, будет вести себя по-разному на разных компиляторах, если только не принять меры для полной определенности при использовании зависимых имен. В частности, особого внимания требует наличие зависимых базовых классов, когда шаблон класса наследуется от одного из параметров этого шаблона (например, T в случае template<typename T>class С:T{};) или от типа, который построен с использованием одного из параметров шаблона (например, X<T> в случае template<typename T>class C:X<T>{};).
Коротко говоря, при обращении к любому члену зависимого базового класса необходимо всегда явно квалифицировать имя с использованием имени базового класса или при помощи this->. Этот способ можно рассматривать просто как некую магию, которая заставляет все компиляторы делать именно то, что вы от них хотите.
template<typename T>
class С : X<T> {
typename X<T>::SomeType s; // Использование вложенного