Chubsdad
Chubsdad

Reputation: 25527

Template specialization with float as non type

Section 4.3 of C++ Templates states 'Not being able to use floating-point literals (and simple constant floating-point expressions) as template arguments has historical reasons.'

Similarly,

$14.1/7 states - "A non-type template-parameter shall not be declared to have floating point, class, or void type. [ Example:

template<double d> class X; // error
template<double* pd> class Y; // OK
template<double& rd> class Z; // OK"
  1. What is the historical reason that is being talked about in the book in the above quote?

  2. Looking at why Y and Z are valid but not X, is the whole challenge related to having non type template parameters of floating type got to do anything with pointers/references?

  3. Why template non type parameters can not be of class type?

Upvotes: 12

Views: 4423

Answers (5)

Ant6n
Ant6n

Reputation: 2007

A possible solution to this problem is to to use a type that has a constant value which is a float, then use that type as a template parameter. For example, if you want to have an integer polynomial, say, and want to evaluate it at some compile-time floating point value:

template <int a, int b, int c>
class Polynomial {
public:
    template <typename t>
    static constexpr float eval() {
        return a*t::value*t::value + b*t::value + c;
    }
};

class THREE_POINT_FIVE {
public:
    static constexpr float value = 3.5f;
};

int main() {
    constexpr float y = Polynomial<2, 0, 1>::typename eval<THREE_POINT_FIVE>();
    std::cout << y << std::endl;
}

One could also use helper classes that allow creating classes of floats, for example for percent:

template <unsigned int p>
class PERCENT {
public:
    static constexpr float value = p * 0.01f;
};

...
constexpr float y2 = Polynomial<2, 0, 1>::typename eval<PERCENT<43>>
...

I guess this is similar to using the std::ratio mentioned before.

Upvotes: 0

kalu
kalu

Reputation: 2682

A solution to this problem is to use rational numbers. Send two integer non-type parameters and then initialize your float in the constructor as follows

template<int dNum =1, int dDen = 3>
class myclass {
  double d;
  myclass: d(dNum/dDen) {}
};

voila, passing a float.

Upvotes: 1

Tony Delroy
Tony Delroy

Reputation: 106196

The exact encoding of floating point values is more subject to quirks of individual CPUs. For example, in evaluating a supposedly constant expression, should a CPU use higher-precision 80bit CPU registers and only round back to 64-bit at the end? If one compilers says yes and another no - the template will get two distinct instantiations. But, some other compiler might only have 64-bit registers, and perhaps different CPUs might vary by an epsilon value. The order in which some compiler chooses to evaluate an expression, or whether a library was compiled using a floating-point emulation library etc. might cause such mis-matches. Further, floating point numbers have some strange edge cases: positive and negative 0 etc., for which behaviour would have to be defined.

These issues may potentially bite in environments where objects are compiled on different machines (with different CPUs, compiler versions and flags etc.), but need to link reliably. Enterprises commonly do this, and binary-distributed libraries face such issues too. C++ compilers generally try to adopt some Application Binary Interface (ABI) that's as consistent as possible across versions and environments, but these wouldn't currently standardise how floating point parameters were calculated, and it's not obvious how they could without e.g. expecting all compilers to use the same software floating-point emulation to derive the values. That would take coordination effort, and existing emulation solutions may have licensing issues.

Interestingly, Walter Bright (of Digital Mars) thought that was all crap and allows floating point constants in D... guess he's been getting some real world experience of the consequences that would be useful to the C++ community, but I haven't heard recently.

Upvotes: 1

Prasoon Saurav
Prasoon Saurav

Reputation: 92874

It could be hard to pick the right template instantitiation, because of possible roundoff errors .

Consider the following:

template<float n> 
void f(n) {...}  //Version 1

template<0.3333> 
void f() { ...} // Version 2:Specialization for 0.3333 

f(1/3); -> Which version would be called?

Consider the following code:

template <float f> class foo { ... }; 
foo<1E6 + 1E-6> my_foo; 

" What should the compiler generate? The compiler has to know about the details of target floating-point architecture to be able to run instantiate the template. This is easy enough if the compiler is running on the target architecture, it can just do the calculation and find out the answer, but if you are cross-compiling, the compiler would have to be able to synthesise the floating point behaviour of every intended target architecture. And thankfully the Standards Committee decided that this would be unreasonable. "

Shamelessly copied from here.

Why template non type parameters can not be of class type

As per my understanding a non-type paramater cannot be of class type because there may be more than one implementation of a class. For example

template <typename T>   
class demo{...};

template <>    
class demo<int>{...};


template <typename T, demo d> //which demo?? demo<T> or demo<int>
class Example{...};

Local classes cannot be used as template parameters because they don't have external linkage.

Upvotes: 10

YeenFei
YeenFei

Reputation: 3218

Floating point numbers have no universal representation (and some values can't even be represented without precision losses since its based on approximation) and therefore can be different from platform to platform causing the same C++ code generate different templates on different platforms.

(One can say as C++ supports cross compilation without requiring the compiler to fully emulate the floating point arithmetic of the target machine. Allowing float or double as a template parameter would invalidate this.)

template<float F> 
float squared_float() 
{ 
return F * F;
}

For example, squared_float<1.0> might be the same function as squared_float<1.00000001> on some implementations, whereas on others they would be two different functions.


A reference to a float means the compiler can optimize it as it knows it's value and that it should never change.

For pointer, well its just another architecture-dependent (32bit/64bit) data type that has nothing to do with float.

Upvotes: 5

Related Questions