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

C++TemplatesGenericsFloating Point

C++ Problem Overview


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.

C++ Solutions


Solution 1 - C++

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 - C++

The current C++ standard does not allow float (i.e. real number) or character string literals to be used as template non-type parameters. You can of course use the float and char * types as normal arguments.

Perhaps the author is using a compiler that doesn't follow the current standard?

Solution 3 - C++

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 4 - C++

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 5 - C++

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 6 - C++

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.

Solution 7 - C++

If you are ok to have a fixed default per type you can create a type to define it as a constant and specialize it as needed.

template <typename T> struct MyTypeDefault { static const T value; };
template <typename T> const T MyTypeDefault<T>::value = T();
template <> struct MyTypeDefault<double> { static const double value; };
const double MyTypeDefault<double>::value = 1.0;

template <typename T>
class MyType {
  public:
    MyType() { value = MyTypeDefault<T>::value; }
  private:
    T value;
 };

If you have C++11 you can use constexpr when defining the default value. With C++14, MyTypeDefault can be a template variable which is a bit cleaner syntactically.

//C++14
template <typename T> constexpr T MyTypeDefault = T();
template <> constexpr double MyTypeDefault<double> = 1.0;

template <typename T>
class MyType {
  private:
    T value = MyTypeDefault<T>;
 };

Solution 8 - C++

You can always fake it...

#include <iostream>

template <int NUM, int DEN>
struct Float
{
    static constexpr float value() { return (float)NUM / (float)DEN; }
    static constexpr float VALUE = value();
};

template <class GRAD, class CONST>
struct LinearFunc
{
    static float func(float x) { return GRAD::VALUE*x + CONST::VALUE; }
};


int main()
{
    // Y = 0.333 x + 0.2
    // x=2, y=0.866
    std::cout << " func(2) = "
              << LinearFunc<Float<1,3>, Float<1,5> > ::func(2) << std::endl;
}

Ref: http://code-slim-jim.blogspot.jp/2013/06/c11-no-floats-in-templates-wtf.html

Solution 9 - C++

The other answers give good reasons why you probably do not want floating point template parameters, but the real deal breaker IMO is that equality using '==' and bitwise equality are not the same:

  1. -0.0 == 0.0, but 0.0 and -0.0 are not bitwise equal

  2. NAN != NAN

Neither kind of equality is a good cancidate for type equality: Of course, point 2. makes using == invalid for determining type equality. One could use bitwise equality instead, but then x != y does not imply that MyClass<x> and MyClass<y> are different types (by 2.), which would be rather strange.

Solution 10 - C++

If you don't need the double to be a compile-time constant, you can pass it in as a pointer:

#include <iostream>

extern const double kMyDouble = 0.1;;

template <const double* MyDouble>
void writeDouble() {
   std::cout << *MyDouble << std::endl; 
}

int main()
{
    writeDouble<&kMyDouble>();
   return 0;
}

Solution 11 - C++

If you only want to represent a fixed precision, then you can use a technique like this to convert a float parameter into an int.

For example an array with a growth factor of 1.75 could be created as follows assuming 2 digits of precision (divide by 100).

template <typename _Kind_, int _Factor_=175>
class Array
{
public:
    static const float Factor;
    _Kind_ * Data;
    int Size;

    // ...

    void Resize()
    {
         _Kind_ * data = new _Kind_[(Size*Factor)+1];

         // ...
    }
}

template<typename _Kind_, int _Factor_>
const float Array<_kind_,_Factor_>::Factor = _Factor_/100;

If you dont like the representation of 1.75 as 175 in the template argument list then you could always wrap it in some macro.

#define FloatToIntPrecision(f,p) (f*(10^p))

template <typename _Kind_, int _Factor_=FloatToIntPrecision(1.75,2)>
// ...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionyokksView Question on Stackoverflow
Solution 1 - C++Filip Roséen - refpView Answer on Stackoverflow
Solution 2 - C++anonView Answer on Stackoverflow
Solution 3 - C++Richard CordenView Answer on Stackoverflow
Solution 4 - C++moonshadowView Answer on Stackoverflow
Solution 5 - C++Andrew GoedhartView Answer on Stackoverflow
Solution 6 - C++Andreas H.View Answer on Stackoverflow
Solution 7 - C++Matthew FioravanteView Answer on Stackoverflow
Solution 8 - C++Ashley SmartView Answer on Stackoverflow
Solution 9 - C++MatthieuView Answer on Stackoverflow
Solution 10 - C++user3233025View Answer on Stackoverflow
Solution 11 - C++jurujenView Answer on Stackoverflow