12

I have problem understanding the following paragraph Per C++11 Standard N3485 Section 14.1.7. I think it is more important to understand the rationale instead of memorizing the facts.

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

—end example ]

I have some questions regarding this rule:

  1. Is there a reason that why floating point type cannot be used as template parameter? What is the rationale behind that? I know this is true before C++11 and it seems also true for C++11 standard.

  2. Why it is OK to use pointer or reference to floating point types as non-template parameters, but not raw floating point type? What is the big difference here?

Thank you for your help.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
taocp
  • 23,276
  • 10
  • 49
  • 62
  • possible duplicate of [Why can't I use float value as a template parameter?](http://stackoverflow.com/questions/2183087/why-cant-i-use-float-value-as-a-template-parameter) – Pietro Saccardi Nov 03 '14 at 13:45

2 Answers2

8

Is there a reason that why floating point type cannot be used as template parameter? What is the rationale behind that?

While I cannot give the ultimate reason, I can definitely imagine there would be problems with specializing a template that accepts a floating pointer value as a parameter.

Equality comparisons between floating point numbers is tricky (in the sense that it sometimes gives unexpected results), and when matching specializations, the compiler would have to perform an equality check between the argument provided and the value for which a template is being specialized.

Another similar issue is determining whether two instances of the same class templates are actually the same type:

template<double D>
struct X
{
    // ...
};

int main()
{
    X<3.0> x;
    X<some_constant_expression()> y;
}

Are x and y instances of the same class? To decide this, an equality check has to be performed between 3.0 and some_constant_expression().

Why it is OK to use pointer or reference to floating point types as non-template parameters, but not raw floating point type?

With respect to the above scenario, the answer concerning pointers is simple: pointers are integral values, and comparison between pointers is well defined.

Concerning references, evidence shows that they are in fact treated like pointers:

#include <type_traits>

double a = 0.0;
double b = 0.0;

template<double& D>
struct X : std::false_type { };

template<>
struct X<a> : std::true_type { }

int main()
{
    static_assert(X<a>::value, "!"); // Does not fire
    static_assert(X<b>::value, "!"); // Fires
}

Also, per paragraph 14.4/1 of the C++11 Standard:

Two template-ids refer to the same class or function if

— [...]

— their corresponding non-type template arguments of integral or enumeration type have identical values and

— [...]

— their corresponding non-type template-arguments of reference type refer to the same external object or function and

— [...]

[ Example:

template<class E, int size> class buffer { / ... / };
buffer<char,2*512> x;
buffer<char,1024> y;

declares x and y to be of the same type, and [...]

The above shows that determining whether two different instances of the same class template are actually the same class requires an equality check between the constant expressions used as template arguments.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • +1 Thank you, Andy. Does compiler always do `equality checking between the argument provided and the value for which a template is being specialized.` ? – taocp May 22 '13 at 14:22
  • 2
    I would not say that the floating poitn comparison is tricky, at least not for the compiler part. It's in fact very simple. But it often gives results that are very surprising for humans, so working with floating point templates would be just a plain unpredictable for devolpers. – Arne Mertz May 22 '13 at 14:24
  • @ArneMertz: Yes, that's what I meant - I may have to rephrase – Andy Prowl May 22 '13 at 14:26
  • @taocp: Yes, although some conversions are allowed (see 14.3.2/5) – Andy Prowl May 22 '13 at 14:30
  • @juanchopanza: It seems they are treated like pointers (see the example I added) – Andy Prowl May 22 '13 at 14:39
  • @juanchopanza: A reference is just a disguised pointer. The compiler can use the same mechanics there. If a given implementation does not implement references using pointers, it can still take the address of the referenced object, since only compiletime constants are allowed. – Arne Mertz May 22 '13 at 14:41
  • @AndyProwl they have to be treated like pointers. Your example cannot use the values of `a` and `b`, because those aren't compile time constants. And of course, the double comparison issue would apply there, too. – Arne Mertz May 22 '13 at 14:46
  • @ArneMertz: Yes, I figured that. I actually was surprised to see that a reference to double is legal at all as a template argument, since I was under the assumption that references are indistinguishable from the objects they are bound to, and therefore a floating-point equality comparison would be necessary for matching specializations. I see the behavior you mention makes sense though – Andy Prowl May 22 '13 at 14:49
  • regarding `float` as non-type template parameters: they should not pose any additional technical challenges. At least Vandevoorde & Josuttis claim in their book that it should be possible. – TemplateRex May 22 '13 at 17:49
  • @rhalbersma: Actually they write that not even `double` should present any technical challenge, and in fact I don't think the problem is a technical challenge, but rather the fact that it would often behave in an unexpected way (comparison of floating point expressions does not always yield intuitive results) – Andy Prowl May 22 '13 at 18:00
2

To figure this out, consider that integral types and pointers always have a one-to-one relationship with their literal representation.

But then let's consider a non-type template Foo<float>.

Let's say it's specialized for a non-binary-representable numver like 0.1: Foo<0.1> and let's say the compiler decorates the symbol name based on the specialization and you wind up with something like Foo_3DCCCCCC because the compiler is using "round down" for an IEEE 754 32 bit rep.

But then let's say that the user of this code is compiling is such a way that the compiler chooses "round to positive infinity" instead. Then then specialization's name is instead Foo_3DCCCCCD which is a completely different function from the one that was previously specialized in another translation unit.

Unless you start making up a wide variety of rules to handle all these sorts of things (what about NaN, infinity, and other not-a-normal-numbers?) then you open yourself to mismatches and all sorts of possible problems.

Mark B
  • 95,107
  • 10
  • 109
  • 188