Упростить большое количество шаблонных специализаций

Таким образом, у меня есть огромное количество специализаций шаблона этого шаблона:

template <typename T> // Same
struct foo { // Same
using type_name = T; // Same
foo(const int base) : _base(base) {} // May take other parameters
void func(const T& param) {} // This function signature will be the same but body will differ
int _base; // Same but may have more members
}; // Same

Таким образом, пример специализации будет:

template<>
struct foo<float> {
using type_name = T;
foo(const int base, const int child) : _base(base), _child(child) {}
void func(const T& param) { cout << param * _child << endl; }
int _base;
int _child;
};

Очевидно, это игрушечный пример и тело _func будет более вовлеченным. Но я думаю, что это выражает идею. Я, очевидно, могу сделать макрос, чтобы помочь с образцом и поместить реализацию специализированной версии функции в файл реализации.

Но я надеялся, что C ++ предоставил мне способ сделать это без макросов. Есть ли другой способ для меня, чтобы избежать написания шаблона снова и снова?

2

Решение

Вы можете иметь несколько специализаций для функции, но не для всего класса

как это

#include <iostream>
#include <string>

template<typename T>
struct foo {
//common generic code
using type_name = T;
foo(const int base, const int child) : _base(base), _child(child) {}
void func(const T& param);
int _base;
int _child;
};

template<>
void foo<float>::func(const type_name&) {
//implementation
std::cout << "float" << std::endl;
}

template<>
void foo<int>::func(const type_name&) {
//implementation
std::cout << "int" << std::endl;
}int main() {
foo<int> tint(0, 0);
foo<float> fint(0, 0);

tint.func(0);
fint.func(0);
}
3

Другие решения

Вы можете использовать некоторое легкое наследование структур данных, чтобы помочь вам отделить различия в разметке элементов и определениях конструкторов от основного шаблона.

//Define an internal aggregate type you can specialize for your various template parameters
template <typename T>
struct foo_data {
foo(const int base) : _base(base) {}
int _base;
};

//Then derive privately from the data struct (or publicly if you really desire)
template <typename T>
struct foo : private foo_data<T> {
using type_name = T;
using foo_data<T>::foo_data<T>; //Make the base class constructors visible
void func(const T& param); //Use member specialization as suggested by the other answer
};

Я оставлю вам решать, лучше ли так или нет, но в итоге все общие части полностью отделены от всех необычных частей.

В комментарии под другим ответом я ошибочно назвал это CRTP. Это не так и не имеет никаких недостатков, как CRTP.

Если вам действительно нужно сохранить стандартную компоновку, то вы можете моделировать наследование вручную с явным делегированием и совершенной пересылкой.

template <typename T>
struct foo {
using type_name = T;
template <typename... Args>
foo(Args&&... args) : base_data_(std::forward<Args>(args)...) {}
void func(const T& param); //Use member specialization as suggested by the other answer
foo_data<T> base_data_;
};

Одним из недостатков является то, что я не думаю, что делегирующий конструктор будет SFINAE правильно, как написано, и это также съедает noexcept спецификаторы и explicit, Решение этих проблем (если необходимо) оставлено читателю в качестве упражнения.

1

Нет хорошего способа избежать некоторой избыточности в нотации при реализации специализаций шаблонных типов. Есть несколько методов, чтобы избежать дублирования реального кода, таких как

  1. Использование шаблона признаков для предоставления специфичных для типа вещей

    template<typename T>
    struct foo_traits { ... };  // provide many specialisations
    
    template<typename T>        // no specialisations
    struct foo
    {
    using traits = foo_traits<T>;
    
    template<typename...Aars>
    explicit foo(Args&&...args)
    : data(std::forward<Args>(args)...) {}
    
    int do_something_specific(T x)
    { return traits::do_something(data,x); }
    private:
    typename traits::data data;
    };
    
  2. очень похожий подход заключается в использовании специализированного базового класса:

    template<typename T>
    struct foo_base { ... };    // provide many specialisations
    
    template<typename T>        // no specialisations
    struct foo : foo_base<T>
    {
    using base = foo_base<T>;
    
    template<typename...Aars>
    explicit foo(int m, Args&&...args)
    : base(std::forward<Args>(args)...)
    , more_data(m) {}
    
    int do_something_specific(T x)
    { return base::do_something(x,more_data); }
    private:
    int more_data;
    };
    

    Конструктор foo является шаблоном с переменными значениями, чтобы конструктор базового класса мог принимать любое число и тип аргументов.

  3. Из вас можно использовать общий базовый класс и специализировать производные классы. Это можно сделать с помощью Любопытно повторяющийся шаблон (CRTP)

    template<typename Derived>
    struct foo_base            // no specializations
    {
    using type = typename Derived::type;
    int do_something(type x)
    {
    auto result = static_cast<Derived*>(this)->specific_method(x);
    return do_some_common_stuff(result);
    }
    protected:
    foo_base(type x) : data(x) {}
    type data;
    private:
    int do_some_common_stuff(type x)
    { /* ... */ }
    };
    
    template<typename T>       // some specialisations
    struct foo : foo_base<foo<T>>
    {
    using base = foo_base<foo>;
    using type = T;
    using common_type = typename base::common_type;
    using base::do_something;
    explicit foo(type x, type y)
    : base(x), extra_data(y) {}
    protected:
    type specific_method(type x)
    { /* ... */ }
    private:
    type extra_data;
    };
    

    Обратите внимание, что foo_base это уже шаблон (в отличие от ситуации с обычным полиморфизмом), так что вы уже можете делать много специфических вещей. Только вещи, которые сделаны по-разному (не только с разными типами), нуждаются в специализации foo,

  4. Наконец, вы можете объединить эти подходы, например, классы признаков с CRTP.

Все эти методы реализуют некоторый тип статический или же время компиляции полиморфизм, а не реальный или динамический полиморфизм: нет виртуальных функций и, следовательно, нет виртуальной таблицы и нет накладных расходов на просмотр таблицы. Все решается во время компиляции.

1

Обычно это делается с помощью наследования — вы помещаете неизменную часть в базовый класс и специализируете детей.

Я не думаю, что вам нужен пример для этого, но дайте мне знать, если вы делаете.

0