Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

understanding of non-type template parameters

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.

like image 441
taocp Avatar asked May 22 '13 14:05

taocp


2 Answers

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.

like image 52
Andy Prowl Avatar answered Sep 19 '22 18:09

Andy Prowl


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.

like image 32
Mark B Avatar answered Sep 21 '22 18:09

Mark B