Expected behavior on out-of-range template parameters? - c++

template<bool b = 2> void foo(void) {}
template void foo();
template<unsigned char n = 258> void bar(void) {}
template void bar();
GCC instantiates foo< true> and bar<2>; Clang rejects both with "error: non-type template argument evaluates to 2, which cannot be narrowed to type 'bool' [-Wc++11-narrowing]".
Is the above code valid? Is this a bug in one of them?
Versions used: Clang 3.8.0-2ubuntu4, GCC 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.2)

This is gcc bug 57891 and 60715.
From [temp.arg.nontype]:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter.
From [expr.const]:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted
expression is a constant expression and the implicit conversion sequence contains only [...] integral conversions (4.7) other than narrowing conversions (8.5.4),
From [dcl.init.list]:
A narrowing conversion is an implicit conversion [...] from an integer type or unscoped enumeration type to an integer type that cannot represent all the
values of the original type, except where the source is a constant expression whose value after integral
promotions will fit into the target type.
Narrowing conversions (e.g. 2 to bool or 258 to char) are ill-formed for template non-type parameters.

Related

Three-way comparison and constexpr function template: which compiler is right?

Consider:
#include <compare>
template<class=void>
constexpr int f() { return 1; }
unsigned int x;
using T = decltype(x <=> f());
GCC and MSVC accept the declaration of T. Clang rejects it, with the following error message:
<source>:7:26: error: argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'
using T = decltype(x <=> f());
^
1 error generated.
(live demo)
If the template-head (template<class=void>) is removed, or if f is explicitly or implicitly instantiated before the declaration of T, then Clang accepts it. For example, Clang accepts:
#include <compare>
template<class=void>
constexpr int f() { return 1; }
unsigned x;
auto _ = x <=> f();
using T = decltype(x <=> f());
(live demo)
Which compiler is correct, and why?
Clang is correct per N4861.
[temp.inst]/5:
Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program.
[temp.inst]/8:
The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression ([expr.const])
[expr.const]/15:
A function or variable is needed for constant evaluation if it is:
a constexpr function that is named by an expression ([basic.def.odr]) that is potentially constant evaluated, or
a variable [...].
[expr.const]/15:
An expression or conversion is potentially constant evaluated if it is:
a manifestly constant-evaluated expression,
a potentially-evaluated expression ([basic.def.odr]),
an immediate subexpression of a braced-init-list,
an expression of the form & cast-expression that occurs within a templated entity, or
a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.
[expr.const]/5:
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
[...]
an invocation of an undefined constexpr function;
[dcl.init.list]/7:
A narrowing conversion is an implicit conversion
[...]
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type
[expr.spaceship]/4:
If both operands have arithmetic types, or one operand has integral type and the other operand has unscoped enumeration type, the usual arithmetic conversions are applied to the operands.
Then:
If a narrowing conversion is required, other than from an integral type to a floating-point type, the program is ill-formed.
[expr.arith.conv]:
[T]he usual arithmetic conversions [...] are defined as follows:
[...]
Otherwise, the integral promotions ([conv.prom]) shall be performed on both operands.
Then the following rules shall be applied to the promoted operands:
[...]
Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.
Since x <=> f() in decltype(x <=> f()) does not satisfy the criteria of being "potentially constant evaluated", f is not "needed for constant evaluation". Therefore, the existence of the definition of f<> is not considered to affect the semantics of the program. Therefore, this expression does not instantiate the definition of f<>.
Therefore, in the original example, f() is a call to undefined constexpr function, which is not a constant expression.
Per the usual arithmetic conversions, in x <=> f(), f() (of type int) is converted to unsigned int. When f() is not a constant expression, this conversion is a narrowing conversion, which renders the program ill-formed.
If f is not a function template, or if its definition has been instantiated, then f() is a constant expression, and because the result of f() fits into unsigned int, the conversion from f() to unsigned int is not a narrowing conversion, and thus the program is well-formed.

Void pointer as template argument in C++

The following does not compile:
template<void *p>
class X {
// ...
};
int r;
int main()
{
X<&r> x;
return 0;
}
The error message is
x.cc:10:6: error: could not convert template argument ‘& r’ to ‘void*’
Explicitly casting &r to (void *) doesn't help either. The error message becomes:
x.cc:10:14: error: could not convert template argument ‘(void*)(& r)’ to ‘void*’
Which part of the standard specifies that behaviour?
The GCC version is gcc version 5.2.1 20151003 (Ubuntu 5.2.1-21ubuntu2)
Edit:
Please note that using e.g. int * instead of void * works as expected.
Edit: (answering myself)
It does not work with gcc HEAD 6.0.0 20151016 (experimental) when specifying -std=c++1z, neither with implicit nor with explicit casting to "void *".
It does work with clang HEAD 3.8.0 (trunk 250513) and has been since (at least) clang 3.6.0 (tags/RELEASE_360/final) when specifying --std=c++1z and explicitly casting to *void *".
Without the explicit cast, clang complains as follows:
x.cc:10:7: error: conversion from 'int *' to 'void *' is not allowed in a converted constant expression
Responsible for fixing this bug in the c++ language specification is N4268 which clang already implements.
Normally, there is a conversion allowed for any pointer to void*.
[C++11, 4.10/2] A prvalue of type “pointer to cv T,” where T is an
object type, can be converted to a prvalue of type “pointer to cv
void”. The result of converting a “pointer to cv T” to a “pointer to
cv void” points to the start of the storage location where the object
of type T resides, as if the object is a most derived object (1.8) of
type T (that is, not a base class subobject). The null pointer value
is converted to the null pointer value of the destination type.
However, for non-type template arguments, certain conversions are specified:
[C++11, 14.3.2/5] The following conversions are performed on each
expression used as a non-type template-argument. If a non-type
template-argument cannot be converted to the type of the corresponding
template-parameter then the program is ill-formed.
[...]
— for a non-type template-parameter of type pointer to object,
qualification conversions (4.4) and the array-to-pointer conversion
(4.2) are applied; if the template-argument is of type std::nullptr_t,
the null pointer conversion (4.10) is applied. [...]
By omission, we can reason this conversion is simply not allowed.
I can't quote you the chapter and verse off the top of my head (edits are welcome), but what you are trying to do is not allowed in c++.
Template parameters must be known at compile time. Pointers are only resolved at link time unless:
they are defaulted using = nullptr in the template argument list.
they are member function pointers (which are known at compile time since they are merely offsets).
for example, this will compile:
template<void * = nullptr>
class X {
// ...
};
int r;
int main()
{
X<nullptr> x;
return 0;
}

How implicit conversion works for non-type template parameters?

I guess (certain) implicit conversions apply when passing non-type template parameters. For example, there should be a conversion from int to std::size_t for expressions like std::array<int, 7>. However, consider the following code:
template <bool>
void f() {
std::cout << "false\n";
}
template <>
void f<true>() {
std::cout << "true\n";
}
int main() {
f<1>();
f<4>();
f<0>();
}
I expect int to be implicitly converted to bool here. But the behaviors are different on VC, GCC, and clang.
On VC, true, false, and false are printed, which is really weird to me.
On GCC, true, true, and false are printed, which is what I expect.
While on clang, the code does not compile at all due to the statement f<4>();.
candidate template ignored: invalid explicitly-specified argument for 1st template parameter
So, what does the standard say about this? What is the implicit conversion rule for non-type template parameters?
From the standard (§14.3.2/5):
The following conversions are performed on each expression used as a non-type template-argument. If a
non-type template-argument cannot be converted to the type of the corresponding template-parameter then
the program is ill-formed.
For a non-type template-parameter of integral or enumeration type, conversions permitted in a converted constant expression (5.19) are applied.
In §5.19, we learn (emphasis mine):
An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted
to a prvalue, where the converted expression is a core constant expression. ... A converted constant expression of
type T is an expression, implicitly converted to a prvalue of type T, where the converted expression is a core
constant expression and the implicit conversion sequence contains only user-defined conversions, lvalue-to-rvalue
conversions (4.1), integral promotions (4.5), and integral conversions (4.7) other than narrowing conversions
(8.5.4). [ Note: such expressions may be used in new expressions (5.3.4), as case expressions (6.4.2),
as enumerator initializers if the underlying type is fixed (7.2), as array bounds (8.3.4), and as integral or
enumeration non-type template arguments (14.3). —end note ]
So narrowing conversions (like converting 4 to bool) are explicitly disallowed for integral constant expressions, which are required in this case as a non-type template argument. That makes the call f<4>() ill-formed.
I believe Clang is correct in issuing an error, and GCC and VC are both nonconforming for not issuing any diagnostic.
[Update] This is GCC Bug #57891, looks like it's currently unassigned.

Calling constexpr in default template argument

In C++11 I am using a constexpr function as a default value for a template parameter - it looks like this:
template <int value>
struct bar
{
static constexpr int get()
{
return value;
}
};
template <typename A, int value = A::get()>
struct foo
{
};
int main()
{
typedef foo<bar<0>> type;
return 0;
}
G++ 4.5 and 4.7 compiles this, but Clang++ 3.1 does not. The error message from clang is:
clang_test.cpp:10:35: error: non-type template argument is not a constant expression
template <typename A, int value = A::get()>
^~~~~~~~
clang_test.cpp:17:19: note: while checking a default template argument used here
typedef foo<bar<3>> type;
~~~~~~~~~^~
clang_test.cpp:10:35: note: undefined function 'get' cannot be used in a constant expression
template <typename A, int value = A::get()>
^
clang_test.cpp:4:23: note: declared here
static constexpr int get()
^
1 error generated.
Which one is correct?
Richard Smith (zygoloid) at the LLVM IRC channel had a short talk with me about this issue which is your answer
<litb> hello folks
<litb> zygoloid, what should happen in this case?
<litb> http://stackoverflow.com/questions/10721130/calling-constexpr-in-default-template-argument
<litb> it seems to be clang's behavior is surprising
<litb> zygoloid, i cannot apply the "point of instantiation" rule to constexpr
function templates. if i call such a function template, the called definition's
POI often is *after* the specialization reference, which means at the point of
the call, the constexpr function template specialization is "undefined".
<zygoloid> it's a horrible mess. Clang does not do what the standard intends, but
as you note, the actual spec is gloriously unclear
<d0k> :(
<zygoloid> we should instantiate bar<3>::get(), because it is odr-used, but we
don't, because we incorrectly believe it's used in an unevaluated context
<zygoloid> conversely, the point of instantiation is too late :/
<zygoloid> PR11851
So it seems that sometimes, Clang instantiates called function templates or member function of class templates but their instantiation is too late for the call to see, and at other cases it doesn't even instantiate them because it thinks it will never need them (unevaluated context).
I think GCC Clang is correct
quoted from n3290:
14.3.2 Template non-type arguments [temp.arg.nontype]
A template-argument for a non-type, non-template template-parameter shall be one of:
for a non-type template-parameter of integral or enumeration type, a converted > constant expression (5.19) of the type of the template-parameter; or
...
EDIT: 5.19 3
A literal constant expression is a prvalue core constant expression of
literal type, but not pointer type. An integral constant expression is
a literal constant expression of integral or unscoped enumeration
type. [ Note: Such expressions may be used as array bounds (8.3.4,
5.3.4), as bit-field lengths (9.6), as enumerator initializers if the underlying type is not fixed (7.2), as null pointer constants (4.10),
and as alignments (7.6.2). —end note ] A converted constant expression
of type T is a literal constant expression, implicitly converted to
type T, where the implicit conversion (if any) is permitted in a
literal constant expression and the implicit conversion sequence
contains only user-defined conversions, lvalue-to-rvalue conversions
(4.1), integral promotions (4.5), and integral conversions (4.7) other
than narrowing conversions (8.5.4).
[ Note: such expressions may be used as case expressions (6.4.2), as enumerator initializers if the underlying type is fixed (7.2), and as integral or enumeration non-type template arguments (14.3). —end note ]

Why can't I downcast pointer to members in template arguments?

If I make a pointer-to-base-member, I can convert it to a pointer-to-derived-member usually, but not when used within a template like Buzz below, where the first template argument influences the second one. Am I fighting compiler bugs or does the standard really mandate this not work?
struct Foo
{
int x;
};
struct Bar : public Foo
{
};
template<class T, int T::* z>
struct Buzz
{
};
static int Bar::* const workaround = &Foo::x;
int main()
{
// This works. Downcasting of pointer to members in general is fine.
int Bar::* y = &Foo::x;
// But this doesn't, at least in G++ 4.2 or Sun C++ 5.9. Why not?
// Error: could not convert template argument '&Foo::x' to 'int Bar::*'
Buzz<Bar, &Foo::x> test;
// Sun C++ 5.9 accepts this but G++ doesn't because '&' can't appear in
// a constant expression
Buzz<Bar, static_cast<int Bar::*>(&Foo::x)> test;
// Sun C++ 5.9 accepts this as well, but G++ complains "workaround cannot
// appear in a constant expression"
Buzz<Bar, workaround> test;
return 0;
}
It simply isn't allowed. According to §14.3.2/5:
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
— for a non-type template-parameter of integral or enumeration type, integral promotions (4.5) and integral conversions (4.7) are applied.
— for a non-type template-parameter of type pointer to object, qualification conversions (4.4) and the array-to-pointer conversion (4.2) are applied.
— For a non-type template-parameter of type reference to object, no conversions apply. The type referred to by the reference may be more cv-qualified than the (otherwise identical) type of the template argument. The template-parameter is bound directly to the template-argument, which must be an lvalue.
— For a non-type template-parameter of type pointer to function, only the function-to-pointer conversion (4.3) is applied. If the template-argument represents a set of overloaded functions (or a pointer to such), the matching function is selected from the set (13.4).
— For a non-type template-parameter of type reference to function, no conversions apply. If the template-argument represents a set of overloaded functions, the matching function is selected from the set (13.4).
— For a non-type template-parameter of type pointer to member function, no conversions apply. If the template-argument represents a set of overloaded member functions, the matching member function is selected from the set (13.4).
— For a non-type template-parameter of type pointer to data member, qualification conversions (4.4) are applied.
I've emphasized the conversion regarding pointer to data members. Note that your conversion (§4.11/2) is not listed. In C++0x, it remains the same in this regard.