Reputation: 10132
Assuming I have a 4 value vector union, used to hold either a spatial coordinate or colour and I wish to use one of two functions to convert between integer and real format, how would I construct the functions?
My (failed) attempt is:
template<class T,
class S,
typename = std::enable_if_t<std::is_floating_point<T>>,
typename = std::enable_if_t<std::is_integral<S>>>
Math::Vec4<T> Colour(S r, S g, S b, S a)
{
return Vec4<T>(r / static_cast<T>(255),
g / static_cast<T>(255),
b / static_cast<T>(255),
a / static_cast<T>(255));
};
template<class T,
class S,
typename = std::enable_if_t<std::is_integral<T>>,
typename = std::enable_if_t<std::is_floating_point<S>>>
Math::Vec4<T> Colour(S r, S g, S b, S a)
{
return Vec4<T>(static_cast<T>(r * 255),
static_cast<T>(g * 255),
static_cast<T>(b * 255),
static_cast<T>(a * 255));
};
The intention here is to instantiate 1 for cases where T is real and S is integer (convert from integer to real) and 2 for cases where T is integer and S is real. Ideally I'd like to use the same name for both and for the compiler to decide which to use based on the input type. At the moment I get a compiler error, "function template has already been defined".
Also, is this a bad idea?
Update: I've produced a minimal repro based on Tartan's answer, that doesn't compile (can't deduce template argument). Where did I err?
#include <limits>
#include <type_traits>
template<class T>
union Vec3
{
typedef T value_type;
struct
{
T r, g, b;
};
Vec3() = default;
Vec3(T R, T G, T B) : r(R), g(G), b(B) {}
};
template<class T,
class S,
std::enable_if_t<std::is_floating_point<T>::value && std::is_integral<S>::value> * = nullptr>
Vec3<T> Colour(Vec3<S> const & rgb)
{
return Vec3<T>(static_cast<T>(rgb.r) / static_cast<T>(std::numeric_limits<S::value_type>::max()),
static_cast<T>(rgb.g) / static_cast<T>(std::numeric_limits<S::value_type>::max()),
static_cast<T>(rgb.b) / static_cast<T>(std::numeric_limits<S::value_type>::max()));
}
template<class T,
class S,
std::enable_if_t<std::is_integral<T>::value && std::is_floating_point<S>::value> * = nullptr>
Vec3<T> Colour(Vec3<S> const & rgb)
{
return Vec3<T>(static_cast<T>(rgb.r * static_cast<S::value_type>(std::numeric_limits<T>::max())),
static_cast<T>(rgb.g * static_cast<S::value_type>(std::numeric_limits<T>::max())),
static_cast<T>(rgb.b * static_cast<S::value_type>(std::numeric_limits<T>::max())));
}
int main(void)
{
Vec3<float> a(1.0f, 0.5f, 0.25f);
Vec3<char> b;
b = Colour(a);
}
Upvotes: 1
Views: 665
Reputation: 15528
The answer of @TartanLlama presents the technical way to go. Here I give my opinion on your more fundamental question
Also, is this a bad idea?
Yes, I think so.
To me it seems your two overloads of Coulour
are inverse to each other. Thus, you should express that and give them proper names.
Nobody would expect Colour(Colour(r,g,b,s))
to be the identity operation (such a function doesn't exists here but the interpretation is clear).
Something like colour_to_spatial
/spatial_to_colour
or colour
/coulor_invert
would be much more appropriate imo.
Next, there might be technical issues. The conversion from Integer
to Floating-point
back to Integer
is in general not deterministic, as rounding errors might occur which lead to different results. So I'd suggest to be careful here and use proper rounding routines, or better, a fixed-width decimal type.
Upvotes: 4
Reputation: 65770
Your issue is that differences in default template arguments don't declare distinct templates. That is why you are getting the redefinition error.
An additional issue is that you need to take the ::value
of the std::is_integral
and std::is_floating_point
traits, as std::enable_if_t
expects a bool
, not a std::integral_constant
.
To fix this, you can use a standard SFINAE trick to declare a template argument of a type which depends on the enable_if_t
result:
template<class T,
class S,
std::enable_if_t<
std::is_floating_point<T>::value && std::is_integral<S>::value
>* = nullptr>
Math::Vec4<T> Colour(S r, S g, S b, S a)
{
return Vec4<T>(r / static_cast<T>(255),
g / static_cast<T>(255),
b / static_cast<T>(255),
a / static_cast<T>(255));
};
template<class T,
class S,
std::enable_if_t<
std::is_integral<T>::value && std::is_floating_point<S>::value
>* = nullptr>
Math::Vec4<T> Colour(S r, S g, S b, S a)
{
return Vec4<T>(static_cast<T>(r * 255),
static_cast<T>(g * 255),
static_cast<T>(b * 255),
static_cast<T>(a * 255));
};
Upvotes: 4