Reputation: 31
I can't specialize some methods in my vec2 template class. Here's my code:
#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*/
}
My problem is: i want to specialize my length
method this way:
it must return a float
if template type is int
(vec2<int>
)
it must return a float
if template type is float
(vec2<float>
)
it must return a double
if template type is double
(vec2<double>
)
I previously specialized my length
method like this:
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;
and then implemented that in my .cpp file. And that works fine but it can only return the same template type, it can't return a float length
for a vec2<int>
. Is there a way to do that?
Upvotes: 2
Views: 423
Reputation: 36
There are several possible options:
1) Use some form of helper traits template to provide the return type for length:
if only specializations for the types you want to store in the template (int, float, double) are provided, then you could get rid of static_assert (although the static assert most likely has advantage when it comes to the readability of the potential error message)
one could also provide specialization of this traits template only for int and provide generic version for everything else, combined with the static_assert already there would work the same but with two less specializations to write, but in my opinion the knowledge about the types handled by your class would then be spread in two places (traits and static assert)
2) In case the implementation for the 'general' case (double and float) is the same and only int case is different - one could also make public length call private length overloads taking false_type/true_type with the result of is_same :), less expandable but without the need for any external types:
auto length() -> decltype(length(is_same<T, int>) { return length(is_same<T, int>); }
Number length(std::false_type) { .... }
float length(std::true_type) {....}
Of course with C++14 one could get rid of decltype mambo-jambo.
Upvotes: 2
Reputation: 45665
You can write a helper type which gives you the return type for length
given the vector component type.
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; };
(or give it a more general name to be reused somewhere else, like floatify
or similar)
Then use it like this:
template<typename Number>
struct vec2 {
...
typename vec_length_t<Number>::type length() const;
...
};
To be reused for multiple functions or uses in the same class, you can of course also use a local type alias:
template<typename Number>
struct vec2 {
...
using length_t = typename vec_length_t<Number>::type;
...
length_t length() const;
...
};
This makes it easy to also use length_t
in the function body to call the correct std::sqrt
overload (you probably don't want to use the double
overload when you are about to return a 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()));
}
Upvotes: 4