Шаблонная специализация класса, где аргумент шаблона является шаблоном
Интересно, возможно ли что-то похожее на это? По сути, у меня есть шаблонный класс, который иногда принимает объекты шаблонных классов. Я хотел бы специализировать его (или просто функцию-член) для определенного шаблонного класса, но для "универсальной" формы этого класса.
template<typename T, typename S>
class SomeRandomClass
{
//put something here
};
template<typename T>
class MyTemplateClass
{
void DoSomething(T & t) {
//...something
}
};
template<>
void MyTemplateClass< SomeRandomClass<???> >::DoSomething(SomeRandomClass<???> & t)
{
//something specialized happens here
}
Замена вопросительных знаков на соответствующие типы (двойные и т. Д.) Работает, но я бы хотел, чтобы он оставался общим. Я не знаю, что там поставить, так как любые типы не были бы определены. Я осмотрелся, узнал о параметрах шаблона и попробовал различные комбинации безрезультатно. Спасибо за помощь!
5 ответов
Можно так специализировать класс
template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
void DoSomething(SomeRandomClass<T,S>& t) { /* something */ }
};
Невозможно специализировать только метод-член, потому что специализация принадлежит классу в целом, и вы должны определить новый класс. Вы можете, однако, сделать
template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
void DoSomething(SomeRandomClass<T,S>& t);
};
template <>
template <typename T,typename S>
void MyTemplateClass<SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S>& t)
{
// something
}
разделить декларацию и определение.
Я не совсем уверен, почему @Ryan Calhoun специализировался так, как он, но вот более краткий пример:
// class we want to specialize with later on
template<typename T, typename S>
struct SomeRandomClass
{
int myInt = 0;
};
// non-specialized class
template<typename T>
struct MyTemplateClass
{
void DoSomething(T & t)
{
std::cout << "Not specialized" << std::endl;
}
};
// specialized class
template<typename T, typename S>
struct MyTemplateClass< SomeRandomClass<T, S> >
{
void DoSomething(SomeRandomClass<T,S> & t)
{
std::cout << "Specialized" << std::endl;
}
};
Вы можете видеть, что вам не нужен избыточный синтаксис, используемый в принятом ответе:
template<>
template<typename T, typename S>
альтернатива
Вы можете использовать type_traits и tag-dispatch в своем специализированном классе, чтобы специализировать только функцию.
Давайте сначала создадим концепцию для is_random_class
:
// concept to test for whether some type is SomeRandomClass<T,S>
template<typename T>
struct is_random_class : std::false_type{};
template<typename T, typename S>
struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};
И тогда давайте объявим наш MyTemplateClass
еще раз, но на этот раз не шаблонный (потому что мы не специализируемся), поэтому мы назовем его MyNonTemplatedClass
:
class MyNonTemplatedClass
{
public:
template<typename T>
void DoSomething(T & t)
{
DoSomethingHelper(t, typename is_random_class<T>::type());
}
// ...
Обратите внимание, как DoSomething
теперь шаблонизируется, и на самом деле он вызывает помощника вместо реализации самой логики?
Давайте сломаем линию:
DoSomethingHelper(t, typename is_random_class<T>::type());
t
как и прежде; мы передаем аргумент типаT&
typename is_random_class<T>::type()
is_random_class<T>
это наша концепция, и так как она вытекает изstd::true_type
или жеstd::false_type
это будет иметь::type
определенный в классе (Google для "черты типа")::type()
'создает экземпляр' типа, указанногоis_random_class<T>::type
, Я говорю это в кавычках, потому что мы действительно собираемся выбросить это, как мы увидим позжеtypename
требуется, потому что компилятор не знает, чтоis_random_clas<T>::type
на самом деле называет тип.
Теперь мы готовы посмотреть на остальные MyNonTemplatedClass
:
private:
//use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
template<typename T>
void DoSomethingHelper(T&t, std::true_type)
{
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
}
template<typename T>
void DoSomethingHelper(T&t, std::false_type)
{
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
Полная рабочая демоверсия v2 здесь
Обратите внимание, что наши вспомогательные функции имеют одинаковые имена, но перегружены типом второго параметра. Мы не даем имя параметру, потому что оно нам не нужно, и, надеюсь, компилятор оптимизирует его, продолжая вызывать правильную функцию.
Наша концепция силы DoSomethingHelper(T&t, std::true_type)
только если T
имеет тип SomeRandomClass
и вызывает другого для любого другого типа.
Преимущество отправки тегов
Основное преимущество диспетчера тегов заключается в том, что вам не нужно специализировать весь ваш класс, если вы хотите специализировать только одну функцию в этом классе.
Диспетчеризация тегов будет происходить во время компиляции, чего вы не получите, если попытаетесь выполнить разветвление для концепции исключительно в пределах DoSomething
функция.
Все, что вам нужно сделать, это просто шаблон того, что вы хотите сохранить в общем. Взяв то, с чего вы начали:
template<typename T, typename S>
void MyTemplateClass< SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S> & t)
{
//something specialized happens here
}
РЕДАКТИРОВАТЬ:
В качестве альтернативы, если вы хотите сохранить только часть SomeRandomClass
В общем, вы могли бы:
template<typename T>
void MyTemplateClass< SomeRandomClass<T,int> >::DoSomething(SomeRandomClass<T,int> & t)
{
//something specialized happens here
}
Изменить: это правильный ответ на другой вопрос.
Использование typename T
дважды смущает проблему, потому что они скомпилированы отдельно и никак не связаны.
Вы можете перегрузить метод, чтобы получить шаблонный параметр:
template <typename T>
class MyTemplateClass
{
void DoSomething(T& t) { }
template <typename U,typename V>
void DoSomething(SomeRandomClass<<U,V>& r) { }
};
Это карты U
а также V
в новом методе T'
а также S'
в SomeRandomClass
, В этой настройке либо U
или же V
может быть того же типа, что и T
, но они не должны быть. В зависимости от вашего компилятора, вы должны быть в состоянии сделать
MyTemplateClass<string> mine;
SomeRandomClass<int,double> random;
// note: nevermind the non-const ref on the string literal here...
mine.DoSomething("hello world");
mine.DoSomething(random);
и шаблонный вызов будет выбран в качестве соответствующей перегрузки без необходимости явного повторного определения типов.
Редактировать:
Работа с шаблонной специализацией не имеет значения для перегрузки DoSomething
, Если вы специализируете класс следующим образом
template <>
class SomeRandomClass <int,double>
{
// something here...
};
тогда приведенная выше перегрузка с удовольствием сожрет эту специализированную реализацию. Просто убедитесь, что интерфейсы специализированного шаблона и шаблона по умолчанию совпадают.
Если вы хотите специализироваться DoSomething
взять определенную пару типов для SomeRandomClass
тогда вы уже потеряли общность... вот что такое специализация.
Если вы хотите использовать структуру шаблона в качестве аргумента шаблона (с намерением использовать ее внутри), не специализируя ее:
Вот пример, который добавляет тип к кортежу с учетом структуры sfinae шаблона в качестве аргумента шаблона:
template<typename Tuple, typename T, template<typename> class /*SFINAEPredicate*/>
struct append_if;
template<typename T, template<typename> class SFINAEPredicate, typename ... Types>
struct append_if<std::tuple<Types...>, T, SFINAEPredicate>
{
using type = typename std::conditional<SFINAEPredicate<T>::value,
std::tuple<Types..., T>, std::tuple<Types...>>::type;
};
// usage
using tuple_with_int = append_if<std::tuple<>, int, std::is_fundamental>;
Это можно использовать, начиная с C++11.