Why Can't I Use Float Value as a Template Parameter

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

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?

How can I pass floating point numbers as template parameters?

This works for me:

template <double* pVar>
double fun()
{
return *pVar;
}

double param = 1.2;

int main() {
double value = fun<¶m>();
return 0;
}

I'm using g++ 4.8.2.

Floating-point types as template parameter in C++20

It seems that the real question that can be answered here is about the history of this feature, so that whatever compiler support can be understood in context.

Limitations on non-type template parameter types

People have been wanting class-type non-type template parameters for a long time. The answers there are somewhat lacking; what really makes support for such template parameters (really, of non-trivial user-defined types) complicated is their unknown notion of identity: given

struct A {/*...*/};
template<A> struct X {};
constexpr A f() {/*...*/}
constexpr A g() {/*...*/}
X<f()> xf;
X<g()> &xg=xf; // OK?

how do we decide whether X<f()> and X<g()> are the same type? For integers, the answer seems intuitively obvious, but a class type might be something like std::vector<int>, in which case we might have

// C++23, if that
using A=std::vector<int>;
constexpr A f() {return {1,2,3};}
constexpr A g() {
A ret={1,2,3};
ret.reserve(1000);
return ret;
}

and it's not clear what to make of the fact that both objects contain the same values (and hence compare equal with ==) despite having very different behavior (e.g., for iterator invalidation).

P0732 Class types in non-type template parameters

It's true that this paper first added support for class-type non-type template parameters, in terms of the new <=> operator. The logic was that classes that defaulted that operator were "transparent to comparisons" (the term used was "strong structural equality") and so programmers and compilers could agree on a definition of identity.

P1185 <=> != ==

Later it was realized that == should be separately defaultable for performance reasons (e.g., it allows an early exit for comparing strings of different lengths), and the definition of strong structural equality was rewritten in terms of that operator (which comes for free along with a defaulted <=>). This doesn't affect this story, but the trail is incomplete without it.

P1714 NTTP are incomplete without float, double, and long double!

It was discovered that class-type NTTPs and the unrelated feature of constexpr std::bit_cast allowed a floating-point value to be smuggled into a template argument inside a type like std::array<std::byte,sizeof(float)>. The semantics that would result from such a trick would be that every representation of a float would be a different template argument, despite the fact that -0.0==0.0 and (given float nan=std::numeric_limits<float>::quiet_NaN();) nan!=nan. It was therefore proposed that floating-point values be allowed directly as template arguments, with those semantics, to avoid encouraging widespread adoption of such a hacky workaround.

At the time, there was a lot of confusion around the idea that (given template<auto> int vt;) x==y might differ from &vt<x>==&vt<y>), and the proposal was rejected as needing more analysis than could be afforded for C++20.

P1907R0 Inconsistencies with non-type template parameters

It turns out that == has a lot of problems in this area. Even enumerations (which have always been allowed as template parameter types) can overload ==, and using them as template arguments simply ignores that overload entirely. (This is more or less necessary: such an operator might be defined in some translation units and not others, or might be defined differently, or have internal linkage, etc.) Moreover, what an implementation needs to do with a template argument is canonicalize it: to compare one template argument (in, say, a call) to another (in, say, an explicit specialization) would require that the latter had somehow already been identified in terms of the former while somehow allowing the possibility that they might differ.

This notion of identity already differs from == for other types as well. Even P0732 recognized that references (which can also be the type of template parameters) aren't compared with ==, since of course x==y does not imply that &x==&y. Less widely appreciated was that pointers-to-members also violate this correspondence: because of their different behavior in constant evaluation, pointers to different members of a union are distinct as template arguments despite comparing ==, and pointers-to-members that have been cast to point into a base class have similar behavior (although their comparison is unspecified and hence disallowed as a direct component of constant evaluation).

In fact, in November 2019 GCC had already implemented basic support for class-type NTTPs without requiring any comparison operator.

P1837 Remove NTTPs of class type from C++20

These incongruities were so numerous that it had already been proposed that the entire feature be postponed until C++23. In the face of so many problems in so popular a feature, a small group was commissioned to specify the significant changes necessary to save it.

P1907R1 (structural types)

These stories about template arguments of class type and of floating-point type reconverge in the revision of P1907R0 which retained its name but replaced its body with a solution to National Body comments that had also been filed on the same subject. The (new) idea was to recognize that comparisons had never really been germane, and that the only consistent model for template argument identity was that two arguments were different if there was any means of distinguishing them during constant evaluation (which has the aforementioned power to distinguish pointers-to-members, etc.). After all, if two template arguments produce the same specialization, that specialization must have one behavior, and it must be the same as would be obtained from using either of the arguments directly.

While it would be desirable to support a wide range of class types, the only ones that could be reliably supported by what was a new feature introduced (or rather rewritten) at almost the last possible moment for C++20 were those where every value that could be distinguished by the implementation could be distinguished by its clients—hence, only those that have all public members (that recursively have this property). The restrictions on such structural types are not quite as strong as those on an aggregate, since any construction process is permissible so long as it is constexpr. It also has plausible extensions for future language versions to support more class types, perhaps even std::vector<T>—again, by canonicalization (or serialization) rather than by comparison (which cannot support such extensions).

The general solution

This newfound understanding has no relationship to anything else in C++20; class-type NTTPs using this model could have been part of C++11 (which introduced constant expressions of class type). Support was immediately extended to unions, but the logic is not limited to classes at all; it also established that the longstanding prohibitions of template arguments that were pointers to subobjects or that had floating-point type had also been motivated by confusion about == and were unnecessary. (While this doesn't allow string literals to be template arguments for technical reasons, it does allow const char* template arguments that point to the first character of static character arrays.)

In other words, the forces that motivated P1714 were finally recognized as inevitable mathematical consequences of the fundamental behavior of templates and floating-point template arguments became part of C++20 after all. However, neither floating-point nor class-type NTTPs were actually specified for C++20 by their original proposals, complicating "compiler support" documentation.

C++ template with float arguments

The language does not allow the use of floating-point types as non-type template arguments. For an extensive discussion, see Why can't I use float value as a template parameter?

How to limit the template parameter to only floating point type

The below code works but I want to make it simpler:

You may combine abbreviated function templates and the std::floating_point concept for a condensed constrained function template definition:

constexpr auto func(std::floating_point auto num) {
return num * 123;
}

Note that this does not include an explicitly specified trailing return type T as in your original approach, but that for the current definition the deduced return type will be decltype(num), which is either float or double (or impl-defined long double). As @Barry points out in the comments below, if you require a trailing return type, say for an overload with a ref- and cv-qualified function parameter, then the brevity gain of abbreviated templates is lost to the added cost of complex trailing return type.

// Contrived example: but if this was the design intent,
// then no: skip the abbreviated function template approach.
constexpr auto func(const std::floating_point auto& num)
-> std::remove_cvref_t<decltype(num)> { /* ... */ }

// ... and prefer
template<std::floating_point T>
constexpr auto func(const T& num) -> T { /* ... */ }

// ... or (preferential)
template<std::floating_point T>
constexpr T func(const T& num) { /* ... */ }

Why are floating point types invalid template parameter types for template functions?

It is not valid because it is not an integral type. There are certain restrictions on nontype template parameters and this is one of them, which says ...

Floating-point numbers and class-type objects are not allowed as nontype template parameters.

template <double VAT>       // ERROR: floating-point values are not
double process(double v) { // allowed as template parameters
return v * VAT;
}

template <std::string name> // ERROR: class-type objects are not
class MyClass { // allowed as template parameters
/* ... */
};

The above is quoted from C++ Templates. I take no credits for it.

The reason why they are not valid for template initialization, as per my understanding, is because types like float and double don't have a defined implementation in C++. So when a template like

template <double VAT> double process(double v);

is initialized with two different double values as

template <(double) 2/3> double process(2.3)

template <(double) 1/3> double process(2.4);

they might not have same bit representation because of double nontype, which confuses the compiler.

Why is double not allowed as a non-type template parameter?

I had always assumed it had to do with matching implementations against each other. Like are these two instances the same or different:

template class foo<10./3.>
template class foo<1./3 * 10.>

They may not generate the same double-precision representation, so the compiler might think of them as different classes. Then you couldn't assign them to each other, etc.

Why float none-type template parameter is illegal in C++? Is there no float const experision?

A possible motivation for this not being allowed by the standard is that identical template parameters can appear different when treated as floats as not all floats are precisely representable. For example, would

C<1.0f/3.0f> c;

and

C<2.0f/6.0f> c;

be of the same type?

takes one float value and immediately ends the program in template c++

In if (opt == 'f' || opt == 'F') you declared numbers as int, not as float.



Related Topics



Leave a reply



Submit