Специализация шаблона для использования типа по умолчанию, если класс typedef не существует
Я пытаюсь написать код, который использует член typedef аргумента шаблона, но хочу указать тип по умолчанию, если аргумент шаблона не имеет этого typedef. Простой пример, который я пробовал:
struct DefaultType { DefaultType() { printf("Default "); } };
struct NonDefaultType { NonDefaultType() { printf("NonDefault "); } };
struct A {};
struct B { typedef NonDefaultType Type; };
template<typename T, typename Enable = void> struct Get_Type {
typedef DefaultType Type;
};
template<typename T> struct Get_Type< T, typename T::Type > {
typedef typename T::Type Type;
};
int main()
{
Get_Type<A>::Type test1;
Get_Type<B>::Type test2;
}
Я бы ожидал, что это напечатает "Default NonDefault", но вместо этого напечатает "Default Default". Я ожидаю, что вторая строка в main() должна соответствовать специализированной версии Get_Type, потому что существует B:: Type. Однако этого не происходит.
Может ли кто-нибудь объяснить, что происходит здесь и как его исправить, или другим способом достичь той же цели?
Спасибо.
Edit:
Георг дал альтернативный метод, но мне все еще интересно, почему это не работает. Согласно документу boost_if docs, способ специализирования шаблона для разных типов выглядит следующим образом:
template <class T, class Enable = void>
class A { ... };
template <class T>
class A<T, typename enable_if<is_integral<T> >::type> { ... };
template <class T>
class A<T, typename enable_if<is_float<T> >::type> { ... };
Это работает, потому что enable_if <true> имеет тип как typedef, но enable_if <false> не делает.
Я не понимаю, как это отличается от моей версии, где вместо использования enable_if я просто использую T:: Type напрямую. Если T:: Type существует, это не будет таким же, как enable_if <true> :: type в приведенном выше примере и вызывать специализацию? И если T:: Type не существует, не будет ли таким же, как enable_if <false> :: type not existing и выбор версии по умолчанию в приведенном выше примере?
Ответы
Ответ 1
Чтобы ответить на ваше дополнение, ваш аргумент специализации передает член typedef и ожидает, что он даст void
как тип. В этом нет ничего волшебного - он просто использует аргумент по умолчанию. Посмотрим, как это работает. Если вы скажете Get_Type<Foo>::type
, компилятор использует аргумент по умолчанию Enable
, который равен void
, а имя типа становится Get_Type<Foo, void>::type
. Теперь компилятор проверяет соответствие каждой частичной специализации.
Список аргументов частичной специализации <T, typename T::Type>
выводится из исходного списка аргументов <Foo, void>
. Это выведет T
в Foo
, а затем заменит это Foo
на второй аргумент специализации, давая окончательный результат <Foo, NonDefaultType>
для вашей частичной специализации. Однако это не совпадает с исходным списком аргументов <Foo, void>
!
Вам нужен способ получения типа void
, как показано ниже:
template<typename T>
struct tovoid { typedef void type; };
template<typename T, typename Enable = void> struct Get_Type {
typedef DefaultType Type;
};
template<typename T>
struct Get_Type< T, typename tovoid<typename T::Type>::type > {
typedef typename T::Type Type;
};
Теперь это будет работать так, как вы ожидаете. Используя MPL, вы можете использовать always
вместо tovoid
typename apply< always<void>, typename T::type >::type
Ответ 2
Вы можете сделать это, используя SFINAE:
template<class T> struct has_type {
template<class U> static char (&test(typename U::Type const*))[1];
template<class U> static char (&test(...))[2];
static const bool value = (sizeof(test<T>(0)) == 1);
};
template<class T, bool has = has_type<T>::value> struct Get_Type {
typedef DefaultType Type;
};
template<class T> struct Get_Type<T, true> {
typedef typename T::Type Type;
};
Ответ 3
Первый шаг: прекратите использование "Тип" и используйте стандартный тип "mpl".
BOOST_MPL_HAS_XXX_DEF(Type)
template < typename T >
struct get_type { typedef typename T::Type type; };
template < typename T >
struct calculate_type : boost::mpl::if_
<
has_Type<T>
, get_type<T>
, boost::mpl::identity<default_type>
>::type {}
typedef calculate_type<A>::type whatever;
Если вы использовали "тип" вместо "Тип" в своих метафайлах, вам не понадобилось бы, чтобы получатель "get_type" преобразовал его и мог просто вернуть T в этом случае.