Специализация шаблона класса С++, без необходимости переопределять все
У меня есть шаблонный класс:
template<typename T>
class A
{
protected:
std::vector<T> myVector;
public:
/*
constructors + a bunch of member functions here
*/
}
Я хотел бы добавить только одну функцию-член, которая будет работать только для 1 заданного типа T. Можно ли вообще это сделать, не специализируясь на классе и не переопределяя все другие уже существующие методы?
Спасибо
Ответы
Ответ 1
Самое простое и чистое решение - использовать static_assert()
в теле метода, отклоняя другие типы, кроме выбранного (в приведенном ниже примере принимаются только целые числа):
#include <type_traits>
#include <vector>
template <typename T>
class A
{
public:
void onlyForInts(T t)
{
static_assert(std::is_same<T, int>::value, "Works only with ints!");
}
protected:
std::vector<T> myVector;
};
int main()
{
A<int> i;
i.onlyForInts(1); // works !
A<float> f;
//f.onlyForInts(3.14f); // does not compile !
}
OK CASE DEMO
NOK CASE DEMO
Это использует тот факт, что компилятор создает экземпляр функции-члена шаблона класса только тогда, когда на самом деле используется (а не при создании экземпляра класса). И с вышеупомянутым решением, когда компилятор пытается это сделать, он терпит неудачу из-за выполнения static_assert
.
Стандартная ссылка на С++:
§ 14.7.1 Неявное создание экземпляра [temp.inst]
-
Если специализация шаблона функции не была явно создана или явно специализирована, специализированная спецификация шаблона неявно создается, когда специализация ссылается в контексте, для которого требуется определение функции. Если не вызов явной специализации шаблона функции или функции-члена явно специализированного шаблона класса, аргумент по умолчанию для шаблона функции или функции-члена шаблона класса неявно создается, когда функция вызывается в контексте, который требует значение аргумента по умолчанию.
-
[Пример:
template<class T> struct Z {
void f();
void g();
};
void h() {
Z<int> a; // instantiation of class Z<int> required
Z<char>* p; // instantiation of class Z<char> not required
Z<double>* q; // instantiation of class Z<double> not required
a.f(); // instantiation of Z<int>::f() required
p->g(); // instantiation of class Z<char> required, and
// instantiation of Z<char>::g() required
}
Ничто в этом примере не требует class Z<double>
, Z<int>::g()
или Z<char>::f()
неявно инстанцирован. - конец примера]
Ответ 2
Да, это возможно в С++ 03 с CRTP (Любопытно повторяющийся шаблон шаблона):
#include <numeric>
#include <vector>
template<typename Derived, typename T>
struct Base
{
};
template<typename Derived>
struct Base<Derived, int>
{
int Sum() const
{
return std::accumulate(static_cast<Derived const*>(this)->myVector.begin(), static_cast<Derived const*>(this)->myVector.end(), int());
}
};
template<typename T>
class A : public Base<A<T>, T>
{
friend class Base<A<T>, T>;
protected:
std::vector<T> myVector;
public:
/*
constructors + a bunch of member functions here
*/
};
int main()
{
A<int> Foo;
Foo.Sum();
}
Ответ 3
В качестве альтернативного решения, которое также работает в простых С++ 03 (в отличие от решений static_assert
или enable_if
), вы можете добавить дополнительный аргумент шаблона по умолчанию, который позволит вам иметь как
специализированной и неспециализированной версии класса. Затем вы можете наследовать свою специализированную версию из неспециализированной версии.
Вот пример фрагмента:
#include <vector>
template<typename T, bool unspecialized = false>
class A
{
protected:
std::vector<T> myVector;
public:
void setVec(const std::vector<T>& vec) { myVector = vec; }
/*
constructors + a bunch of member functions here
*/
};
template<>
class A<int, false> : public A<int, true>
{
public:
int onlyForInt() {
return 25;
}
};
int main() {
// your code goes here
std::vector<int> vec;
A<int> a;
a.setVec(vec);
a.onlyForInt();
return 0;
}
Недостатками этого решения является необходимость добавления форвардеров конструкторов, если класс
имеет нетривиальные конструкторы.
Ответ 4
Метод static_assert
by @PiotrS. работает красиво. Но также приятно знать, что вы можете специализировать одну функцию-член без дублирования кода. Просто дайте generic onlyForInts()
пустую реализацию no-op и специализируйте ее вне класса для int
#include <vector>
template <typename T>
class A
{
public:
void onlyForInts(T t)
{
// no-op
}
protected:
std::vector<T> myVector;
};
template<>
void A<int>::onlyForInts(int t)
{
// works
}
int main()
{
A<int> i;
i.onlyForInts(1); // works !
A<float> f;
f.onlyForInts(3.14f); // compiles, but does nothing !
}
Пример Live.
Этот метод пригодится, если вы хотите иметь специфическое поведение int
без полного отключения общего поведения.
Ответ 5
Один из подходов, еще не приведенный в ответах, заключается в использовании стандартной библиотеки std::enable_if
для выполнения SFINAE в базовом классе, который вы наследуете к основной класс, который определяет соответствующие функции-члены.
Пример кода:
template<typename T, class Enable = void>
class A_base;
template<typename T>
class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>{
public:
void only_for_ints(){/* integer-based function */}
};
template<typename T>
class A_base<T, typename std::enable_if<!std::is_integral<T>::value>::type>{
public:
// maybe specialize for non-int
};
template<typename T>
class A: public A_base<T>{
protected:
std::vector<T> my_vector;
};
Этот подход будет лучше, чем пустая функция, потому что вы более строго относитесь к вашему API и лучше, чем static_cast
, потому что он просто не будет попадать внутрь функции (она не будет существовать) и даст вам хорошее сообщение об ошибке во время компиляции (GCC показывает "на моей машине не имеет имени с именем" only_for_ints ").
Недостатком этого метода было бы время компиляции и раздувание кода, но я не думаю, что он слишком здоров.
(разве вы не смеете утверждать, что требование С++ 11 является нисходящим, мы в 2014 году бог-damnit, и следующий стандарт уже завершен уже!)
Кроме того, я заметил, вам, вероятно, придется определять my_vector
в базовом классе вместо окончательного, потому что вы, вероятно, захотите обработать эти данные в функции-члене.
Хороший способ сделать это, не дублируя кучу кода, - создать базовый базовый класс (хороший бог) и наследовать этот класс в базовом классе.
Пример:
template<typename T>
class base_data{
protected:
std::vector<T> my_vector;
};
template<typename T>
class A_base<T, typename std::enable_if<std::is_integral<T>::value>::type>: public base_bata<T>{
public:
void only_for_ints(){/* phew, finally. fiddle around with my_vector! */}
};
// non-integer A-base
template<typename T>
class A: public A_base<T>{
protected:
// helper functions not available in base
};
Это оставляет ужасную схему множественного наследования, но она очень работоспособна и упрощает определение членов на основе параметров шаблона (для будущей проверки).
Люди часто не любят множественное наследование или как выглядит сложный/грязный SFINAE, но я не мог жить без него сейчас, когда я знаю об этом: скорость статического кода с полиморфизмом динамического кода!
Ответ 6
Не уверен, где я нашел это, но вы можете использовать = delete;
в качестве определения функции внутри класса, тем самым удаляя функцию для общего случая, а затем явно специализироваться вне класса:
template <typename T>
struct A
{
auto int_only(T) -> void = delete;
};
template <> auto A<int>::int_only(int) -> void {}
int main()
{
auto a_int = A<int>{};
auto a_dbl = A<double>{};
a_int.int_only(0);
// a_dbl.int_only(3.14); error: call to deleted member function
}
https://en.cppreference.com/w/cpp/language/function#Deleted_functions