Template specialization with float as non type
Asked Answered
P

5

12

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?

Prae answered 27/9, 2010 at 3:13 Comment(5)
@aaa carp: Yes, I could figure that out. I am curious what's the historical reason for disallowing floating point types. Also I am trying to understand why X is ill-formed by not Y or Z.Prae
groups.google.com/group/comp.lang.c++/browse_thread/thread/…Sauger
@aaa carp: no, it's not. The address passed would only be known to the linker, but the compiler already needs it to instantiate.Duisburg
@MSa ok, stand correctedSauger
class type simply have no way to compare equal. The compiler can't know how the value of a class type object is defined beyond comparing bitpatterns. And how will you mangle a class type value?Villalobos
A
10

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.

Ashes answered 27/9, 2010 at 3:33 Comment(9)
0.3333 is of type 'double' isn't it which is same as the type of 1/3? So the compiler can chose the specialization for 'double' with default as <0.3333>Prae
that sounds reasonable, IEEE repr was introduced in 1985, the same time C++ became "official", so perhaps there was not standard mathematics for floating-pointSauger
@Prae : See this comp.lang.c++ threadAshes
I know that 'value representation of floating point types is implementation defined'. Is there any issue with 1E6 + 1E-6? Also I did not quiet get the issue with cross compilationPrae
Are class types not supported because they may in turn contain 'floating point' types and hence run into the same issue?Prae
@Prae : 1E6 + 1E-6 may not be stored precisely as a post in that thread says. Your compiler should be able to synthesise the floating point behaviour of every intended architecture. But how would that be possible without knowing the exact architecture(in case of cross compilation), does that make sense?Ashes
@Prae : Wait, my my last comment was incorrect (so deleted it). Local classes/structs cannot be used as template parameters because they have internal(not external) linkage, which currently would cause problems for template instantiation. I think C++1x would allow local classes to be used a template parameters.Ashes
But the quote in OP does not mention only about local class/structs. It says non type parameters are not allowed to be of class typePrae
@Prae : Updated my answer in response to your comment.Ashes
C
5

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.

Compartment answered 27/9, 2010 at 3:26 Comment(1)
On a given platform however that should not be an issue. It may however affect portability, which is true of so many other features in the language e.g. sizeof(int)Prae
R
1

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.

Rider answered 27/9, 2010 at 3:34 Comment(3)
there are some MPL stuff in boost vault which allow FP constants.Sauger
hmmm... I avoid MPL - annoyed boost refused to house Loki while the available online docs for MPL are the worst in the whole boost collection and IMHO a blatant "I'm gonna make some money here because I'm in control of boost" advert for books, while other boost authors are expected to actually live by the general "share it freely with the community" spirit boost likes to publicly espouseRider
@Johannes: Yes, but he's already commented on this a while back on comp.lang.c++ or ~.moderated... anyone interested might search there before bothering him.Rider
C
1

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.

Callis answered 5/12, 2011 at 14:30 Comment(1)
But how do you turn the std::ratio into a float?Viscose
V
0

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.

Viscose answered 31/3, 2016 at 23:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.