Why can't I use float value as a template parameter?

61,107

Solution 1

THE SIMPLE ANSWER

The standard doesn't allow floating points as non-type template-arguments, which can be read about in the following section of the C++11 standard;

14.3.2/1      Template non-type arguments      [temp.arg.nontype]

A template-argument for a non-type, non-template template-parameter shall be one of:

  • for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter;

  • the name of a non-type template-parameter; or

  • a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or

  • a constant expression that evaluates to a null pointer value (4.10); or

  • a constant expression that evaluates to a null member pointer value (4.11); or

  • a pointer to member expressed as described in 5.3.1.


But.. but.. WHY!?

It is probably due to the fact that floating point calculations cannot be represented in an exact manner. If it was allowed it could/would result in erroneous/weird behavior when doing something as this;

func<1/3.f> (); 
func<2/6.f> ();

We meant to call the same function twice but this might not be the case since the floating point representation of the two calculations isn't guaranteed to be exactly the same.


How would I represent floating point values as template arguments?

With C++11 you could write some pretty advanced constant-expressions (constexpr) that would calculate the numerator/denominator of a floating value compile time and then pass these two as separate integer arguments.

Remember to define some sort of threshold so that floating point values close to each other yields the same numerator/denominator, otherwise it's kinda pointless since it will then yield the same result previously mentioned as a reason not to allow floating point values as non-type template arguments.

Solution 2

Just to provide one of the reasons why this is a limitation (in the current standard at least).

When matching template specializations, the compiler matches the template arguments, including non-type arguments.

By their very nature, floating point values are not exact and their implementation is not specified by the C++ standard. As a result, it is difficult to decide when two floating point non type arguments really match:

template <float f> void foo () ;

void bar () {
    foo< (1.0/3.0) > ();
    foo< (7.0/21.0) > ();
}

These expressions do not necessarily produce the same "bit pattern" and so it would not be possible to guarantee that they used the same specialization - without special wording to cover this.

Solution 3

Indeed, you can't use float literals as template parameters. See section 14.1 ("A non-type template-parameter shall have one of the following (optionally cv-qualified) types...") of the standard.

You can use a reference to the float as a template parameter:

template <class T, T const &defaultValue>
class GenericClass

.
.

float const c_four_point_six = 4.6; // at global scope

.
.

GenericClass < float, c_four_point_six> gcFlaot;

Solution 4

Wrap the parameter(s) in their own class as constexprs. Effectively this is similar to a trait as it parameterizes the class with a set of floats.

class MyParameters{
    public:
        static constexpr float Kd =1.0f;
        static constexpr float Ki =1.0f;
        static constexpr float Kp =1.0f;
};

and then create a template taking the class type as a parameter

  template <typename NUM, typename TUNING_PARAMS >
  class PidController {

      // define short hand constants for the PID tuning parameters
      static constexpr NUM Kp = TUNING_PARAMS::Kp;
      static constexpr NUM Ki = TUNING_PARAMS::Ki;
      static constexpr NUM Kd = TUNING_PARAMS::Kd;

      .... code to actually do something ...
};

and then use it like so...

int main (){
    PidController<float, MyParameters> controller;
    ...
    ...
}

This allows the compiler to guarantee that only a single instance of the code is created for each template instantiation with the same parameter pack. That gets around all the issues and you are able to use floats and doubles as constexpr inside the templated class.

Solution 5

Starting with C++20 this is possible.

This also gives the answer to the original question:

Why can't I use float value as a template parameter?

Because nobody implemented it in the standard yet. There is no fundamental reason.

In C++20 non-type template parameters can now be floats and even class objects.

There are some requirements on class objects (they must be a literal type) and fulfil some other requirements to exclude the pathological cases such as user defined operator == (Details).

We can even use auto

template <auto Val>
struct Test {
};

struct A {};
static A aval;
Test<aval>  ta;
Test<A{}>  ta2;
Test<1.234>  tf;
Test<1U>  ti;

Note that GCC 9 (and 10) implements class non-type template parameters, but not for floats yet.

Share:
61,107
yokks
Author by

yokks

still searching.......

Updated on February 17, 2022

Comments

  • yokks
    yokks about 2 years

    When I try to use float as a template parameter, the compiler cries for this code, while int works fine.

    Is it because I cannot use float as a template parameter?

    #include<iostream>
    using namespace std;
    
    template <class T, T defaultValue>
    class GenericClass
    {
    private:
        T value;
    public:
        GenericClass()
        {
            value = defaultValue;
        }
    
        T returnVal()
        {
            return value;
        }
    }; 
    
    
    int main()
    {
        GenericClass <int, 10> gcInteger;
        GenericClass < float, 4.6f> gcFlaot;
    
        cout << "\n sum of integer is "<<gcInteger.returnVal();
        cout << "\n sum of float is "<<gcFlaot.returnVal();
    
        return 0;       
    }
    

    Error:

    main.cpp: In function `int main()':
    main.cpp:25: error: `float' is not a valid type for a template constant parameter
    main.cpp:25: error: invalid type in declaration before ';' token
    
    main.cpp:28: error: request for member `returnVal' in `gcFlaot',
                        which is of non-class type `int'
    

    I am reading "Data Structures for Game Programmers" by Ron Penton, the author passes a float, but when I try it it doesn't seem to compile.

  • Admin
    Admin over 14 years
    You can. but it doesn't do the same thing. You can't use the reference as a compile-time constant.
  • Potatoswatter
    Potatoswatter almost 12 years
    The C++11 solution is <ratio>, described by §20.10 as "Compile-time rational arithmetic". Which cuts right to your example.
  • Filip Roséen - refp
    Filip Roséen - refp almost 12 years
    @Potatoswatter afaik there isn't any method in the STL to convert a float into numerator/denominator using <ratio>?
  • Gearoid Murphy
    Gearoid Murphy over 11 years
    This may have some relevance: stackoverflow.com/questions/95727/…
  • Richard J. Ross III
    Richard J. Ross III almost 11 years
    A float != rational number. The two are very separate ideas. One is calculated via a mantissa & an exponent, the other is, well, a rational - not every value representable by a rational is representable by a float.
  • alfC
    alfC over 9 years
    it should be ...::Factor = _Factor_/100.0; otherwise it will be integer division.
  • tmyklebu
    tmyklebu over 9 years
    This doesn't really give a convincing explanation. The whole point of floating-point is that it does represent values exactly. You're free to treat the numbers you have as approximations to something else, and it's often useful to do so, but the numbers themselves are exact.
  • Aaron McDaid
    Aaron McDaid almost 9 years
    This is almost an argument to ban floats entirely from the language. Or, at a minimum, ban the == operator :-) We already accept this inaccuracy at runtime, why not at compile time too?
  • Filip Roséen - refp
    Filip Roséen - refp over 8 years
    @tmyklebu I changed the wording some time ago, but I forgot to send you a PING about it; are you more satisfy with the usage of "that floating point calculations cannot be represented in an exact manner" - instead of saying that floating points in themselves cannot be exact (since, as you point out, that is a bit misleading)
  • tmyklebu
    tmyklebu over 8 years
    @FilipRoséen-refp: All floating-point numbers are exact. Floating-point arithmetic is well-defined on every target I know of. Most floating-point operations produce floating-point results. I can appreciate the committee not wanting to force compiler implementors to implement the target's possibly-bizarre floating-point arithmetic, but I don't believe "the arithmetic is different from integer arithmetic" is a good reason to forbid floating-point template arguments. It's an arbitrary restriction at the end of the day.
  • iheanyi
    iheanyi over 8 years
    @tmyklebu I'm not sure how that applies to the standard. Is floating-point arithmetic well-defined by the standard? If not, that every target you know of has a well-defined implementation is immaterial.
  • tmyklebu
    tmyklebu over 8 years
    @iheanyi: Does the standard say what 12345 * 12345 is? (It does allow int template parameters even though it doesn't specify the width of a signed int or whether that expression is UB.)
  • einpoklum
    einpoklum about 8 years
    Agree with @AaronMcDaid , this is not much of an argument. So you need to be careful in the definition. So what? As long as it works for stuff you get from constants, it's already quite the improvement.
  • einpoklum
    einpoklum about 8 years
    A reference is probably better, see @moonshadow 's answer
  • Ant6n
    Ant6n about 8 years
    Does this actually properly reduce at compile time?
  • Caleth
    Caleth almost 7 years
    @RichardJ.RossIII A float is very definitely a rational number, but there are floats that aren't representable as ratios of two ints. The mantissa is an Integer, and the 2^exponent is an Integer
  • Kemin Zhou
    Kemin Zhou almost 5 years
    ratio (rational number) is a subset of float (real number) in mathematics. However, it is important to remember not to use float or double as non-type template parameter.
  • jeremyong
    jeremyong over 4 years
    @KeminZhou I see this misconception everywhere. Floats are NOT real numbers. Not even close. In fact, they are a subset of the rationals (easily provable). There is a strict injection of every floating point number to the rationals.
  • Andreas H.
    Andreas H. almost 4 years
    C ++20 now allows for float (an other object types) as non-type template parameters. Still C++20 does not specify the float implementation. This shows that einpoklum and Aaron have a point.
  • saxbophone
    saxbophone over 3 years
    If C++20 is an option, the intent can be made even clearer here by passing a struct or class instance by value into the template (as shown in stackoverflow.com/a/60244416/6177253) so typename TUNING_PARAMS could be replaced with MyParameters params within the template arguments. This could be useful to make sure that only the correct type defining needed params is passed. Perhaps a reference could also be passed, in which case subclasses might be passed also..?
  • user3520616
    user3520616 over 3 years
    However, there is a workaround specified here: open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1714r1.html (at the end of the first chapter)
  • FrankHB
    FrankHB over 2 years
    There are already enumerators (with the same underlying integer value) able to be template arguments and they are not identical in the sense of type equivalence, so your first point is not an issue.
  • FrankHB
    FrankHB over 2 years
    @tmyklebu False. 1. High-level languages do not necessarily map the floating-point values to exact notion of the numbers, even not mathematical one. For example, the Scheme language defines the concept of inexact numbers, which are implemented based on floating-point values in almost every practical implementations. (And R6RS even defines flonums.) C++ does not define the model of "C++ number", but since it conforms to LIA-1(ISO/IEC 10967), there is room to be inexact even in the representation, unless IEC 60559 is followed.
  • FrankHB
    FrankHB over 2 years
    2. The certainty of your exactness depending on the definition of the target. For example, given x87 is a popular target, your assumption is simply invalid: sometimes 80287 behaves differently to 80387 onward. 3. Compiler options can easily turns out lots of incompatible floating-point implementations, which may incur unpredicatable behavior by design. For example, the GCC manual states "rounding is unpredictable" for -mfpmath=sse+387, so -fexcess-precision=standard makes no sense.
  • FrankHB
    FrankHB over 2 years
    4. Finally, to implement every details of floating-point arithmetic correctly is HARD. There are tons of hardware not strictly conforming to the standard in corner cases, but you may neglect the bugs before you're bitten.
  • FrankHB
    FrankHB over 2 years
    @jeremyong Only finite floating-point numbers are in the subset of rational numbers. Typically, floating-point numbers include infinite values, which are NOT real numbers, but hyperreals. And floating-point datum can encode various NaN (Not a Number) values. Moreover, +0.0 and -0.0 can be different floating-point numbers, but mathematically equal.
  • Filip Roséen - refp
    Filip Roséen - refp about 2 years
    @FrankHB well put, upvoting.