Is Gcc Considering Builtins of Non-Constant Expression Functions to Be Constant Expressions

__builtin_round is not a constant expression

I have an idea of another workaround.

There is: use a helper function pass_through

template<typename T>
constexpr T&& pass_through (T&& t) { return static_cast<T&&>(t); }

use it like this:

static constexpr double value () { return 1.23; }
static constexpr double result = __builtin_round(pass_through(__builtin_sqrt(value())));

This code is compiled without error in the G++.

I also agree, the opinion that this issue should be reported to the GCC.

static_assert evaluates non constant expression

static_assert evaluates non constant expression

Nope, it most certainly does not. Constant evaluation has a strict set of conditions to is must obey in order to succeed.

For starters:

[dcl.dcl]

6 In a static_assert-declaration, the constant-expression shall be a contextually converted constant expression of type bool.

"contextually converted" is standard lingo for "we'll consider explicit conversion operators". The place where it may become counter-intuitive is when "converted constant expression" is defined.

[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

  • user-defined conversions,
  • [...]

The fine point is in the first sentence of the paragraph. The converted expression must be a constant expression. But the source expression doesn't have to be! So long as the conversion sequence is limited to the list in the paragraph and is valid constant evaluation itself, we are in the clear. In your example, the expression registryv2<0>::x has type constant<true>, it can be contextually converted to a bool via the user defined conversion operator. And well, the conversion operator satisfiers all the requirements of a constexpr function and constant evaluation.

The list of requirements for constant evaluation is rather long so I won't go over it to verify every bullet is upheld. But I will demonstrate that we can trip one of them.

template<auto x> struct constant {
bool const x_ = x;
constexpr explicit operator auto() const { return x_; }
};

This change immediately makes the godbolt code sample ill-formed. Why? Because we are doing an lvalue-to-rvalue conversion (standard lingo for access) on a bool when it is not permitted.

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • an lvalue-to-rvalue conversion unless it is applied to

    • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or

    • a non-volatile glvalue that refers to a subobject of a string literal, or

    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or

    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

Going over the exceptions, none apply. So now registryv2<0>::x is not a contextually converted constant expression of type bool.

This also explain why true__1 is verboten. Same issue, access to an object that is disallowed.



1 - That's a reserved identifier. Two consecutive underscores belong to the implementation for any use. Not critical to the issue at hand, but take note.


Why can I call a non-constexpr function inside a constexpr function?

The program is ill-formed and requires no diagnostic according to the C++11 draft standard section 7.1.5 The constexpr specifier paragraph 5 which says:

For a constexpr function, if no function argument values exist such
that the function invocation substitution would produce a constant
expression (5.19), the program is ill-formed; no diagnostic required.

and provides the following example:

constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required

and section 5.19 paragraph 2 says:

A conditional-expression is a core constant expression unless it
involves one of the following as a potentially evaluated subexpression
[...]

and includes:

— an invocation of a function other than a constexpr constructor for a
literal class or a constexpr function [ Note: Overload resolution
(13.3) is applied as usual —end note ];

We would probably prefer a diagnostic in this case, it could just be an oversight, I have a bug report for a similar situation where gcc does not produce an error but we would probably like it to: Is the compiler allowed leeway in what it considers undefined behavior in a constant expression?.

Update

Using the -fno-builtin flag will cause gcc to generate the following error:

 error: call to non-constexpr function 'int printf(const char*, ...)'
return printf("a side effect!\n");
^

So gcc does consider this ill-formed it is just ignores it when it is using the builtin version of printf.

Although somewhat inconsistently using the -pedantic produces the following warning:

warning: ISO C++ forbids variable length array 'a' [-Wvla]
char a[f()];
^

Note that using f() to initialized a constexpr variable:

constexpr int x = f() ;

does generate an error:

error: 'printf(((const char*)"a side effect!\012"))' is not a constant expression

Note that additionally in the more general case a compiler is not allowed mark standard library functions as constexpr unless explicitly allowed by the standard.

Is it possible to use std::char_traitschar in constant expressions before GCC 8?

This improvement was backported from mainline. It is available in the gcc 7 line from 7.3 onwards.



Related Topics



Leave a reply



Submit