Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type conversion at template non-type argument without constexpr

Consider the following code:

struct A {
    constexpr operator int() { return 42; }
};

template <int>
void foo() {}

void bar(A a) {
    foo<a>();
}

int main() {
    foo<A{}>();

    const int i = 42;
    foo<i>();  // (1)

    A a{};

    static_assert(i == a, "");
    bar(a);
    foo<a>();  // error here
}

Clang 3.7 with c++14 accepts this, while gcc 5.2.0 with c++14 does not, producing the following message:

/tmp/gcc-explorer-compiler1151027-68-1f801jf/example.cpp: In function 'int main()':
26 : error: the value of 'a' is not usable in a constant expression
foo<a>();
^
23 : note: 'a' was not declared 'constexpr'
A a{};
^
Compilation failed

Changing a to be constexpr as suggested by gcc fixes the gcc compilation error, but without constexpr, which compiler is right?

For me, it seems that a should be "usable in constant expression", as static_assert ceritifies. Moreover, the fact that i can be used the same way (marked (1)), and the fact that bar() compiles, also makes me think that gcc is wrong.

UPD: reported a bug against gcc: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68588

like image 714
Petr Avatar asked Nov 27 '15 12:11

Petr


People also ask

What is false constexpr?

In a constexpr if statement, the value of condition must be a contextually converted constant expression of type bool. If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

What is the point of constexpr?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.

What is a non type template parameter?

A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.


2 Answers

The user-defined conversion is allowed by [expr.const]/(4.1), and I don't see a single applicable bullet point in [expr.const]/2 that would prevent your expression from being a constant one. In fact, the requirements are so loose that declaring a as

A a;

is still giving a well-formed program, even if a didn't have a constexpr default constructor etc., since the conversion operator is constexpr and no members are evaluated.

As you saw yourself, GCC is contradictory in that it allows a in the static_assert condition but not a template-argument.

like image 188
Columbo Avatar answered Oct 26 '22 23:10

Columbo


I would say that Clang is correct.

Draft for current C++ (n4296) says:

14.3.2 Template non-type arguments [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

And 5.20 §4 says (emphasize mine):

5.20 Constant expressions [expr.const]

...

(4) 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

(4.1) — user-defined conversions, ...

IFAIK in foo<a>(); a is converted to int with a constexpr user-defined conversion and as such is a converted constant expression.

That being said, we are not far from a edge case here, and my advice would be: do not play with such a construct in production code :-)

like image 24
Serge Ballesta Avatar answered Oct 27 '22 01:10

Serge Ballesta