Могу ли я переслать аргументы шаблона производного класса в базу CRTP?
Предположим, у меня есть производный класс Derived
, А также принимая параметры шаблона, Derived
является производным классом Base
который, в свою очередь, основан на Derived
,
Ниже приведен пример рабочего решения:
template <int i, typename T>
class Derived : public Base<Derived<i,T>>
{
};
template <typename DerivedType>
class Base
{
};
Когда список аргументов шаблона Derived
однако становится большим, потому что кодерам, которые хотят добавить производные классы в библиотеку, приходится дважды писать аргументы шаблона. Есть ли способ как-нибудь автоматизировать это?
Вот то, что я после (следующее не компилируется, потому что this
еще не существует, но это иллюстрирует то, что я ищу):
template <int i, typename T>
class Derived : public Base<decltype(*this)>
{
};
template <typename DerivedType>
class Base
{
};
Я открыт для элегантного макро решения (возможно, оксюморон?), Если нет способа добиться этого с помощью шаблонов.
2 ответа
template <int i, typename X>
struct Derived
{
class T : public Base<T> { ... };
};
Derived<2, char>::T x;
Используйте класс признаков для хранения метаданных, а не для передачи каждого элемента в списке параметров.
Вот что я делаю в библиотеке, которую я пишу, скоро будет открытыми.
Во-первых, есть класс свойств по умолчанию, чтобы охватить общий случай. Я хочу обработать ряд общих случаев, так что это также шаблон, но в противном случае это может быть обычный класс. Параметризация - это только то, что удобно пользователю, а не окончательная детальная параметризация реализации, которая вместо этого включает в себя ее содержимое.
template< typename rep_type, unsigned mantissa_values, rep_type fractional_cycles >
struct positive_logarithm_default_traits {
typedef double conv_basis;
static constexpr bool range_check = true;
typedef rep_type rep;
protected:
static constexpr rep max_rep = std::numeric_limits< rep >::max();
static constexpr rep unity_rep = mantissa_values * fractional_cycles;
// Another specialization could overflow to INFINITY and underflow to 0.
[[noreturn]] static rep underflow() { throw range_error( false ); }
[[noreturn]] static rep overflow() { throw range_error( true ); }
};
Затем я определяю метафункцию для преобразования одного экземпляра класса в другой. Он работает в пространстве классов признаков, что может помочь компилировать время, исключая создание экземпляров промежуточных результатов, если несколько преобразований метаобработки связаны друг с другом.
// The traits of a logarithm which represents the inverse of another logarithm.
template< typename traits >
struct inverse_traits : traits {
static constexpr decltype( traits::unity_rep ) unity_rep
= traits::max_rep - traits::unity_rep;
};
Хотя класс traits обычно содержит только данные времени компиляции, я допускаю изменения во время выполнения, наследуя их. В таких случаях класс признаков также может захотеть получить доступ к состоянию производного класса. Это по сути CRTP. Но для данного класса признаков может потребоваться обслуживать несколько конечных производных классов с параметризацией без признаков. Поэтому я делаю дополнительный класс с состоянием времени выполнения, доступный для класса черт, static_cast< logarithm_state_base< traits > >( * this )
- это функционально эквивалентно CRTP, но обходит большую сложность метапрограммирования.
template< typename traits >
class logarithm_state_base : public traits {
protected:
typename traits::rep log_value;
};
Наконец, производный класс предоставляет пользователю тот же удобный интерфейс, который интерпретирует класс черт по умолчанию. Однако внутренне он ссылается на все метаданные через членов, унаследованных от класса признаков.
Если пользователь определяет свой собственный класс признаков, то параметры шаблона перед typename traits_type
(кроме mantissa_values) являются рудиментарными и не используются. Шаблон псевдонима может установить их все void
обеспечить исключительно основанный на чертах пользовательский интерфейс. С другой стороны, если бы я ожидал, что использование признаков будет более популярным, я мог бы сделать это другим способом и позволить признакам быть "родным" интерфейсом, а детализированные параметры - псевдонимом удобства.
template<
typename rep, // Underlying representation type
unsigned mantissa_values, // # distinct values per power of 2
rep fractional_cycles = std::numeric_limits< rep >::max() / ( mantissa_values * 2 ) + 1,
typename traits_type = positive_logarithm_default_traits< rep, mantissa_values, fractional_cycles >
>
class static_positive_logarithm
: public logarithm_state_base< traits_type > {
static_assert ( std::is_unsigned< typename traits_type::rep >::value,
"Representation type must be unsigned." );
…