float as template parameter

closed account (3hM2Nwbp)
Unless I'm missing something, it's now possible(ish)? A little concept is below, very rough around the edges. Still though, if this is valid by standard C++, why can't we have built-in support for float / double template parameters?

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
#include <iostream>
#include <tuple>

template<int Numerator, int Denominator>
struct Float
{
    constexpr static float value()
    {
        return static_cast<float>(Numerator) / static_cast<float>(Denominator);
    }
};

template <class T>
constexpr T constexpr_pow(T const& x, std::size_t n)
{
    return n > 0 ? x * constexpr_pow(x, n - 1) : 1;
}

constexpr std::tuple<int, int> break_float(float f, int pow = 0, bool negate = false)
{
    return f < 0 ? break_float(-f, pow, true) : 
            (f - static_cast<int>(f) > 0 ? 
              break_float(f * 10, pow + 1, negate) : 
                std::make_tuple(static_cast<int>(negate ? -f : f), 
                                static_cast<int>(constexpr_pow(10, pow))));
}

template<typename F>
void test()
{
    std::cout << F::value() << std::endl;
}

int main()
{
    // Should figure out how to sprinkle some syntatic sugar all over this.  The whole bag of sugar.
    test<Float<std::get<0>(break_float(0.17875)), std::get<1>(break_float(0.17875))>>();
    test<Float<std::get<0>(break_float(-0.17875)), std::get<1>(break_float(-0.17875))>>();
}


0.17875
-0.17875


*Fixed for readability
Last edited on
Try instantiating a template with a boolean parameter by comparing if the float value is greater than zero.
> 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
closed account (3hM2Nwbp)
Can't the same platform constraints be said about integral types as well?

1
2
template<int i> void x() {}

x<65537>(); // what if 'int' is 16 bits on this platform? 
Yes, sizeof(int) is implementation defined. With integral types, compile-time evaluations give precisely the same result as run-time evaluations. If overflow or narrowing conversions are not involved, the evaluations are identical across different platforms.

This is not the case with floating-point types.
See:http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Fproguide%2Fref%2Ffp_folding.htm
@Luc Lieber: That's why I always use this header:
http://en.cppreference.com/w/cpp/header/cstdint
closed account (3hM2Nwbp)
Agreed, fixed width integral types have been missing for far too long.

I see now there's no point in trying to hack around this to make a template based range checker. Non-IEEE-754 compliant systems ruined it for everyone.
Last edited on
> Non-IEEE-754 compliant systems ruined it for everyone.

In C++11, std::numeric_limits<>::is_iec559 is a constexpr (true almost everywhere)

We can use std::enable_if<> and std::conditional<> on it.
Topic archived. No new replies allowed.