Специализация шаблона только для определенных методов

Я не могу специализировать некоторые методы в своем классе шаблонов vec2. Вот мой код:

#pragma once

template<typename Number>
struct vec2
{
    static_assert(std::is_same<Number, int>::value
               || std::is_same<Number, float>::value
               || std::is_same<Number, double>::value,
               "Type not allowed. Use <int>, <float> or <double>.");
    Number x, y;

    vec2();
    vec2(Number x, Number y);

    void add(const vec2& other);
    inline Number lengthSquared() const;
    /*Some other general methods like this two*/
}

Моя проблема: я хочу специализировать свой метод length следующим образом:
он должен возвращать float, если тип шаблона int (vec2<int>)
он должен возвращать float, если тип шаблона float (vec2<float>)
он должен возвращать double, если тип шаблона double (vec2<double>)

Ранее я специализировал свой метод length следующим образом:

struct vec2
{
/* ... */

inline Number length() const;
}
/*Outside vec2 struct, but in vec2.h*/
template<> inline int vec2<int>::length() const;
template<> inline float vec2<float>::length() const;
template<> inline double vec2<double>::length() const;

а затем реализовал это в моем файле .cpp. И это прекрасно работает, но может возвращать только тот же тип шаблона, он не может возвращать float length вместо vec2<int>. Есть ли способ сделать это?


person GiaDif    schedule 14.07.2015    source источник


Ответы (2)


Возможны несколько вариантов:

1) Используйте некоторую форму шаблона вспомогательных черт, чтобы предоставить тип возвращаемого значения для длины:

  • если предоставлены только специализации для типов, которые вы хотите сохранить в шаблоне (int, float, double), то вы можете избавиться от static_assert (хотя статическое утверждение, скорее всего, имеет преимущество, когда речь идет о читабельности потенциального сообщения об ошибке )

  • можно также предоставить специализацию этого шаблона признаков только для int и предоставить общую версию для всего остального, в сочетании с static_assert уже будет работать то же самое, но с двумя меньшими специализациями для написания, но, на мой взгляд, знание о типах, обрабатываемых вашим Затем класс будет распространяться в двух местах (черты и статическое утверждение)

2) В случае, если реализация для «общего» случая (двойного и плавающего) одинакова, а отличается только случай int - можно также сделать перегрузки частной длины вызова общедоступной длины, принимая false_type/true_type с результатом is_same :), меньше расширяемый, но без необходимости каких-либо внешних типов:

auto length() -> decltype(length(is_same<T, int>) { return length(is_same<T, int>); }
Number length(std::false_type) { .... }
float length(std::true_type) {....}

Конечно, с C++14 можно было бы избавиться от мамбо-джамбо decltype.

person altariste    schedule 14.07.2015

Вы можете написать вспомогательный тип, который даст вам тип возвращаемого значения для length с учетом типа компонента вектора.

template<typename T>
struct vec_length_t {};

// Specializations:
template<>
struct vec_length_t<int> { using type = float; };

template<>
struct vec_length_t<float> { using type = float; };

template<>
struct vec_length_t<double> { using type = double; };

(или дайте ему более общее имя для повторного использования в другом месте, например floatify или подобное)

Затем используйте его следующим образом:

template<typename Number>
struct vec2 {
    ...
    typename vec_length_t<Number>::type length() const;
    ...
};

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

template<typename Number>
struct vec2 {
    ...
    using length_t = typename vec_length_t<Number>::type;
    ...
    length_t length() const;
    ...
};

Это упрощает использование length_t в теле функции для вызова правильной перегрузки std::sqrt (вероятно, вы не захотите использовать перегрузку double, когда собираетесь вернуть float!):

template<typename Number>
vec2<Number>::length_t vec2<Number>::length() const {
    // Note that x*x+y*y is a Number, but we want a length_t:
    return std::sqrt(static_cast<length_t>(x*x + y*y));
    // Or, if you have lengthSquared() defined as returning a Number:
    return std::sqrt(static_cast<length_t>(lengthSquared()));
}
person leemes    schedule 14.07.2015