> Still though, if this is valid by standard C++,
> why can't we have built-in support for float / double template parameters?
The problem is that the textual representations of floating point values (constexpr) do not necessarily have the same value on different systems. And two different textual representations may map to the same value on some platforms, and different values on others.
Let us assume for a moment that
constexpr float was allowed as a non-type template parameter:
1 2 3 4 5 6 7 8 9 10 11
|
template< float value > struct X {} ; // assume that this is ok
void function( X< 1.0 > ) {}
void function( X< 1.00000000001 > ) {} // is this a valid overload?
int main()
{
function( X< 1.0000000000999999 >() ) ; // is the function defined?
// is it void function( X< 1.00000000001 > ) ?
}
|
We have an absurdity: This is a valid C++ program on certain platforms and an ill-formed program on others.
We need not even get into issues like:
a. Should a cross-compiler emulate (at compile-time) the floating point environment of the target platform?
b. What should happen if the floating point environment is modified at runtime (header <cfenv>)?
The syntactic sugar part is fairly straightforward (though it would not mitigate the fact that things are somewhat more deceptive than they appear at fist sight; floating point is messy):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
#include <iostream>
#include <iomanip>
#include <limits>
#include <type_traits>
static constexpr float inf = std::numeric_limits<float>::infinity() ;
template < int N, int D = 1 > struct rational
{
static constexpr float value = float(N) / D ;
static constexpr int numerator = N ;
static constexpr int denominator = D ;
};
template < int N > struct rational<N,0>
{
static constexpr float value = inf ;
static constexpr int numerator = 1 ;
static constexpr int denominator = 0 ;
};
namespace detail_
{
template < typename T >
constexpr T pow( T x, std::size_t n ) { return n ? x * pow( x, n-1 ) : 1 ; }
constexpr int numerator_of( float f, std::size_t p = 0 )
{
return f==inf? 1 : f<0 ? -numerator_of( -f, p ) :
f == int(f) ? f : numerator_of( f*10, p+1 ) ;
}
constexpr int denominator_of( float f, std::size_t p = 0 )
{ return f==inf? 0 : f == int(f) ? pow( 10, p ) : denominator_of( f*10, p+1 ) ; }
}
#define RATIONAL(f) rational< detail_::numerator_of(f), detail_::denominator_of(f) >
template < typename F > void test()
{ std::cout << F::value << " (" << F::numerator << '/' << F::denominator << ")\n" ; }
#define TEST(f) ( ( std::cout << std::setw(12) << #f << " => " ), test< RATIONAL(f) >() )
int main()
{
using ten = std::integral_constant< int, RATIONAL(10.0)::numerator > ;
std::cout << std::fixed << std::setprecision( ten::value ) ;
// looks fine
TEST(0.17875) ;
TEST(-235.19) ;
TEST(inf/100.0) ;
// these don't look so good
TEST(0.2/3.0) ; // fine
constexpr float f = 0.02/3.0 ;
// TEST(f) ; // ** error: overflow (platform dependant)
TEST(1000.1001) ;
TEST(1000.10009) ;
TEST(1000.10008) ;
TEST(1000.10007) ;
TEST(1000.10006) ;
TEST(1000.10005) ;
TEST(1000.100049) ;
TEST(1000.1000001) ;
}
void foo( RATIONAL(1000.10008) ) {}
void foo( RATIONAL(1000.100059) ) {} // is this a valid overload? (depends on the platfom)
// void foo( RATIONAL(1000.100061) ) {} // what about this? (depends on the platfom)
void bar( RATIONAL(1000.100079) ) {}
void baz() { bar( RATIONAL(1000.100081){} ) ; } // would this compile? (depends on the platfom)
|
http://coliru.stacked-crooked.com/a/10a718e81f70ac18