I have a class template, intended to be templated on floating points.
The class has a method that should allow the inputs to be widened if needed (eg float
-> double
), but narrowing (ie- loss of precision) should not be allowed.
How would I go about enforcing this?
The following example is the best I've managed to do (using if constexpr
, so at least c++17), but it feels to me that using both a not-implemented specialization and a static_cast
is probably not the cleanest, clearest solution.
It actually does the job as described, provided only float
s and double
s are used (as opposed to more esoteric floating point types), which is not an issue for me:
#include <vector>
#include <type_traits>
template <typename Real>
class Foo
{
public:
template <typename Real2>
void DoSomething( const std::vector<Real2>& v );
};
// Enter here only when input is of same type as template
template <typename Real>
template <typename Real2>
void Foo<Real>::DoSomething( const std::vector<Real2>& v)
{
if constexpr ( !std::is_same<Real, Real2>::value )
static_assert(false);
// ... Do actual work here ...
}
// Allow widening of inputs
template <>
template <>
void Foo<double>::DoSomething( const std::vector<float>& v)
{
std::vector<double> v2;
v2.reserve( v.size() );
for ( const auto& val : v )
v2.push_back( (double)val );
DoSomething(v2);
}
// Deny narrowing of inputs
template <>
template <>
void Foo<float>::DoSomething( const std::vector<double>& v);
int main()
{
std::vector<float> v1;
std::vector<double> v2;
// ... populate both v1 & v2 ...
Foo<float> b1;
b1.DoSomething( v1 );
//b1.DoSomething( v2 ); // <--- This line should not be allowed to compile
Foo<double> b2;
b2.DoSomething( v1 );
b2.DoSomething( v2 );
}
How could I simplify the implementation to achieve the stated goal in a cleaner way?
Edit: changed the incorrect terms 'Upcasting' & 'Downcasting' to 'Widening' & 'Narrowing'
Edit2: somehow I marked the wrong line expected not to compile.
You can do this in many ways, for example with concepts:
#include <vector>
template <typename Real>
class Foo
{
public:
template <typename Real2>
requires requires (Real r) {Real2{r};} // <-- here
void DoSomething( const std::vector<Real2>& v );
};
Or, with a named concept producing a clearer error message:
template <typename A, typename B>
concept is_not_narrower = requires(B b) {A{b};};
template <typename Real>
class Foo
{
public:
template <typename Real2>
requires is_not_narrower<Real2, Real>
void DoSomething( const std::vector<Real2>& v );
};