Класс Шаблон специализация для нескольких типов - PullRequest
3 голосов
/ 29 марта 2019

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

У меня есть шаблон класса, который должен принимать каждый тип. Простой пример:

template <class T>
class State {
  public:
    void set(T newState);
    T get();
  private:
    T state;
};

template <class T>
void State<T>::set(T newState){
  state = newState;
}

template <class T>
T State<T>::get(){
  return state;
}

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

F.e. эта специализация для типа int, но вместо того, чтобы писать это только для типа int, я также хотел бы разрешить все другие варианты int и float. Я нашел std :: is_arithmetic, но понятия не имею, как его использовать для достижения этой цели.

template <>
class State <int> {
  public:
    void set(int newState);
    int get();
    int multiplyState(int n);
  private:
    int state;
};

void State<int>::set(int newState){
  state = newState;
}

int State<int>::get(){
  return state;
}

int State<int>::multiplyState(int n){
  return state*n;
}

Ответы [ 2 ]

5 голосов
/ 29 марта 2019

Вы можете использовать частичную специализацию шаблона в сочетании с SFINAE для достижения этой цели:

#include <type_traits>

template <class T, typename = void>
class State
{
    T state;

public:
    void set(T newState)
    {
        state = newState;
    }

    T get()
    {
      return state;
    }
};

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{
    T state;

public:
    void set(int newState)
    {
        state = newState;
    }

    int get()
    {
        return state;
    }

    int multiplyState(int n)
    {
        return state*n;
    }
};

живой пример здесь

Хитрость здесь заключается в использованииВторой параметр шаблона (который может быть безымянным и имеет аргумент по умолчанию).Когда вы используете специализацию вашего шаблона класса, например, State<some_type>, компилятор должен выяснить, какой из шаблонов следует использовать.Чтобы сделать это, он должен каким-то образом сравнить заданные аргументы шаблона с каждым шаблоном и решить, какой из них лучше всего подходит.

Способ, которым на самом деле выполняется это сопоставление, заключается в попытке вывести аргументы каждой частичной специализации изданный шаблон аргументов.Например, в случае State<int> аргументы шаблона будут int и void (последний существует из-за аргумента по умолчанию для второго параметра основного шаблона).Затем мы пытаемся вывести аргументы для нашей единственной частичной специализации

template <typename T>
class State<T, std::enable_if_t<std::is_arithmetic_v<T>>>;

из аргументов шаблона int, void.Наша частичная специализация имеет единственный параметр T, который может быть напрямую выведен из первого аргумента шаблона как int.И с этим мы уже закончили, так как мы вывели все параметры (здесь только один).Теперь подставим выведенные параметры в частичную специализацию: State<T, std::enable_if_t<std::is_arithmetic_v<T>>>.В итоге мы получим State<int, void>, который соответствует списку начальных аргументов int, void.Следовательно, применяется частичная специализация шаблонов.

Теперь, если вместо этого мы написали State<some_type>, где some_type не является арифметическим типом, то процесс будет таким же, вплоть до того момента, когда мыуспешно вывели параметр для частичной специализации как some_type.Мы снова подставляем параметр обратно в частичную специализацию State<T, std::enable_if_t<std::is_arithmetic_v<T>>>.Тем не менее, std::is_arithmetic_v<some_type> теперь будет false, что приведет к тому, что std::enable_if_t<…> не будет определено, и замена не удастся.Поскольку ошибка замены не является ошибкой в этом контексте, это просто означает, что частичная специализация здесь не вариант, и вместо нее будет использоваться первичный шаблон.

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

2 голосов
/ 29 марта 2019

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

// StateBase only contains the extra multiplyState member when State tells it to
// define it, based on T being an arithmetic type
template <class D, class T, bool has_multiply>
struct StateBase {};

template <class D, class T>
struct StateBase<D, T, true> {
    T multiplyState(int n) {
        return static_cast<D*>(this)->state * n;
    }
};

template <class T>
class State : public StateBase<State<T>, T, std::is_arithmetic<T>::value> {
  public:
    // no need to duplicate these declarations and definitions
    void set(T newState);
    T get();
  private:
    // note that we write State::StateBase to force the injected-class-name to be found
    friend struct State::StateBase;
    T state;
};

ссылка Coliru

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...