Поймать все черты классов, как std::iterator_traits
полезны, отделяя свойства типа от его определения, поэтому, например, свойства могут быть доступны до завершения определения.
Определение черт в дополнение к каждому клиентскому классу само по себе неудобно, потому что черты обычно также имеют место в качестве членов. Вот почему общая реализация std::iterator_traits
определяется в терминах членов своего шаблонного аргумента.
template< typename it >
struct iterator_traits {
typedef typename it::category category;
typedef typename it::value_type value_type;
// etc
};
Разве не проще и меньше работы для компилятора, вместо этого использовать наследование?
template< typename t >
struct t_traits : public t {
t_traits() = delete; // Prevent runtime instances.
};
Это не позволяет документировать интерфейс в основном шаблоне, но в любом случае для этого есть и другие возможности.
Кажется бессмысленным писать много повторяющегося кода для определения класса мета-контейнера, в идиоме, которая даже не гарантирует предотвращения такого злоупотребления, как создание во время выполнения.
Или, может быть, это полностью назад. В дополнение к std::iterator_traits
у нас также есть std::iterator
псевдо-абстрактный базовый класс, состоящий в основном из одинаковых членов. Такая избыточность является запахом кода. Разве не было бы лучше, если бы пользовательские итераторы выглядели так?
template<>
struct iterator_traits< struct my_iterator > {
typedef random_access_iterator_tag category;
typedef foo value_type;
...
};
struct my_iterator : iterator_traits< struct my_iterator > {
...
};
(Ради аргумента давайте проигнорируем тот факт, что фактический std::iterator_traits
специализация должна быть объявлена в namespace std
, Я пытаюсь сделать знакомую иллюстрацию того, что может произойти в коде пользователя.)
Это чище в том смысле, что идиома не должна нарушаться для того, чтобы справиться с любым исключительным случаем, который требовал в первую очередь изысканной работы ног. Вместо шаблона первичных черт, приводящего к внутренней ошибке, что отсутствующий клиентский класс для чего-то не подходит, вообще не должно быть никакого шаблона первичных черт.
Концептуально лучше отделить качества класса от реализации его услуг, независимо от того, необходимо ли это разделение. НО, этот стиль требует разбиения каждого клиентского класса на две части, включая явную специализацию, что довольно уродливо.
Кто-нибудь знаком с этим дизайном пространства? Я склоняюсь ко второй фразеологии, хотя на практике это выглядит необычно. Но есть, вероятно, входы и выходы, известные тем, кто ходил сюда раньше.
Проблема с пользовательскими чертами как специализацией типа библиотеки состоит в том, что тип библиотеки принадлежит библиотеке. Определение явной специализации требует открытия пространства имен библиотеки, что ужасно.
Альтернативы 1 и 2 могут быть объединены в лучший из двух миров, который
Дополнительный кусок клея необходим в форме мета-функции на основе ADL, отображающей любой класс на его характеристики.
template< typename t >
t traits_type_entry( t const & ); // Declared, never defined.
template< typename t >
using traits_type = decltype( traits_type_entry( std::declval< t >() ) );
По умолчанию, T
служит свой собственный тип черт как traits_type< T >::type
является T
, Чтобы изменить это для данного типа t
к классу черт t_traits
, объявить (но не определить) функцию t_traits traits_type_entry( t const & )
, это t_traits
класс может или не может быть базовым классом t
; traits_type
объекту все равно. Поскольку функция будет найдена путем поиска аргумента-зависимости, она может быть объявлена как функция друга без объявления в области имен.
Использование, вложенное в класс (просто для создания сложного тестового примера), выглядит следующим образом. Для обычного использования в пространстве имен просто бросьте friend
ключевое слово.
class outer_scope {
struct special;
struct special_traits {
typedef int value_type;
constexpr static int limit = 5;
};
friend special_traits traits_type_entry( special const & );
struct unspecial {
typedef double baz_type;
int table[ util::traits_type< special >::limit ];
};
struct special : special_traits {
void f() {
std::pair< typename util::traits_type< unspecial >::baz_type,
value_type >();
}
};
};
Обратите внимание t const &
параметр для traits_type_entry
может быть просто t
пока класс является копируемым и разрушаемым.
Кроме того, вы можете предотвратить объявление объекта с (не настроенным) типом черты, если основной шаблон возвращает тип, полученный из t
с удаленным конструктором вместо t
сам.
Других решений пока нет …