I am trying to implement basic arithmetic operations for a Vector class and would like to support mixing underlying types while preventing narrowing from occurring.
template <typename T1,typename T2>
Vector<T1> operator+( Vector<T1> lhs, const Vector<T2>& rhs, std::enable_if< ! is_narrowing_conversion<T2,T1>::value >::type* = nullptr )
{ return lhs += rhs; }
I would like to implement is_narrowing_conversion so that it will only allow the conversion if the types do not narrow. Here are some examples:
Vector<double> a = Vector<double>() + Vector<float>(); //OK
Vector<float> a = Vector<float> + Vector<double>; //Fails to find the operator+ function
In the end I would like to write a second template operator+ function that will handle the inverse case by returning a Vector.
I found this post with an incomplete example. But it is insufficient as he notes that it disallows uint8_t to uint64_t conversions.
I also discovered Daniel Krügler's paper on fixing is_constructible. Specifically in this paper he mentions using list-initialization which already has the narrowing semantics working but I'm not sure how to convert what he mentions into a proper trait that I can use for SFINAE template deduction.
What about using SFINAE through std::common_type
?
The following is a simplified example (no Vector
but simples values; no operator+()
but a sum()
function) but I hope you can understand what I mean
#include <iostream>
#include <type_traits>
template <typename T1, typename T2>
T1 sum (T1 t1,
T2 const & t2,
typename std::enable_if<std::is_same<
T1,
typename std::common_type<T1, T2>::type
>::value>::type * = nullptr)
{ return t1 += t2; }
int main()
{
float a = 1.1f;
double b = 2.2;
long double c = 3.3;
std::cout << sum(b, a) << std::endl;
std::cout << sum(c, a) << std::endl;
std::cout << sum(c, b) << std::endl;
// std::cout << sum(a, b) << std::endl; compilation error
// std::cout << sum(a, c) << std::endl; compilation error
// std::cout << sum(b, c) << std::endl; compilation error
}