What Is Allowed in a Constexpr Function

What is Allowed in a constexpr Function?

It's virtually guaranteed that if there's a discrepancy gcc has the correct behavior, because Visual Studio 2015 doesn't support c++14's extension of constexpr: https://msdn.microsoft.com/en-us/library/hh567368.aspx#C-14-Core-Language-Features

C++11 constexpr functions

The function body can only contain:

  • null statements (plain semicolons)
  • static_assert declarations
  • typedef declarations and alias declarations that do not define classes or enumerations
  • using declarations
  • using directives
  • exactly one return statement

So c++11 cannot tolerate the definition of decltype(div(T{}, T{})) x{}. It would however be acceptable to roll the ternary suggested here in a constexpr function to achieve the same results:

template <typename T>
constexpr auto make_div(const T quot, const T rem)
{
using foo = decltype(div(T{}, T{}));

return foo{1, 0}.quot != 0 ? foo{quot, rem} : foo{rem, quot};
}

Live Example

C++14 constexpr functions

The function body may contain anything but:

  • an asm declaration
  • a goto statement
  • a statement with a label other than case and default
  • a try-block
  • a definition of a variable of non-literal type
  • a definition of a variable of static or thread storage duration
  • a definition of a variable for which no initialization is performed

Where a "Literal Type" is defined here, specifically for objects though, they may be aggregate types with a trivial destructor. So div_t definitely qualifies. Thus c++14, and by extension gcc, can tolerate the definition of decltype(div(T{}, T{})) x{}.

C++17 constexpr functions

C++17 added support for closure types to the definition of "Literal Type", so I find it strange that both gcc and Visual Studio support the use of the lambda in the return statement. I guess that's either forward looking support or the compiler chose to inline the lambda. In either case I don't think that it qualifies as a c++14 constexpr function.

[Source]

does all the functions inside a constexpr function in constexpr context must be constexpr function?

does all the functions inside a constexpr function in constexpr context must be constexpr function?

It depends.

The fact that calls to non-constexpr functions are allowed in constexpr functions doesn't mean that all possible calls to the constexpr function must result in a constant expression, but for context in which a constant expression is needed (like in array bounds), the call to the constexpr function must evaluate to a constant expression

The related parts of the standard for this case are:

[dcl.constexpr]/5

For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (8.20), or, for a constructor, a constant initializer for some object (6.6.2), the program is ill-formed, no diagnostic required.

[expr.const]/2

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

  • (2.2) an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor [...]

This means that a constexpr function can have on its body calls to non-constexpr functions as long as there exist some arguments for which it evaluate to a constant expression or subexpression thereof, that's the reason why you can use foo(1) as value for the array bound, because it evaluation doesn't involve the call to bar() which is not the case for foo(-1).

Why is std::swap allowed in this constexpr function?

Here is a relevant quote from cppreference:

A constexpr function must satisfy the following requirements:

  • ...
  • there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression

There is a path that does not go through std::swap(), where a a>=b. In fact, for gcd(32, 12) the execution never goes through std::swap().

EDIT: I had a look at the C++14 draft. Section 7.1.5 The constexpr specifier. Paragraph 5 says:

For a non-template, non-defaulted constexpr function [...], if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.20), or, for a constructor, a constant
initializer for some object (3.6.2), the program is ill-formed;

and the example they give is:

constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK

I am confused about a constexpr function?

It's not contradictory. As well as mandating that the return type must be of "literal type", the draft standard states that a call to a constexpr function does not have to appear in a constant expression. From the C++11 draft standard:

§7.1.5/7 A call to a constexpr function produces the same result as
a call to a equivalent non-constexpr function in all respects
except that a call to a constexpr function can appear in a constant
expression.

Function is not usable as a 'constexpr' function

In order to evaluate this:

  if constexpr (p(x)) {

We need for p(x) to be a constant expression. The rules for whether something qualifies as a constant expression or not are based on a list of things that you're not allowed to do.

When x is a basic_type<int> and p is a function that takes a basic_type<int> by value, there are simply no rules that we are violating. This is an empty type, so copying it (as we're doing here) doesn't actually involve any kind of read. This just works.


But when x is an int and p is a function that takes an int by value, this also requires copying x but this time it involves reading the value of x. Because, of course, gotta initialize the parameter somehow. And this does run afoul of a rule: [expr.const]/8 says we're not allowed to perform:

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

  • a non-volatile glvalue that refers to an object that is usable in constant expressions, or
  • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;

An lvalue-to-rvalue conversion is what happens when we read the value of a variable, and neither of those bullets apply. It doesn't matter here that you don't actually care what the value is, since p doesn't use it. In order to be able to even invoke p, you have to copy x, and you're not allowed to do that. Hence the error.


However, your lambda here doesn't actually need the value, just the type, so you could instead write this:

    return foo(
[]<typename T>(T const&)
{
return std::is_integral_v<std::decay_t<T>>;
},
0);

Now we're no longer copying x into the lambda, since the lambda no longer takes by value - it takes by reference. As a result, we're not violating the lvalue-to-rvalue conversion rule (or any other rule) and this is now a valid constant expression.


Then, as a bonus, if you change foo to take x by reference (because, again, you don't actually care about the value, so why not):

consteval auto foo(auto p, auto const& x) noexcept {
if constexpr (p(x)) {
return 1;
} else {
return 0;
}
}

Then both variants become ill-formed. Both the basic_type<int> and int versions (regardless of whether you take the int by value or by reference). For more on this case, see the constexpr array size problem which I'm currently trying to resolve with P2280.

What's the difference between constexpr and const?

Basic meaning and syntax

Both keywords can be used in the declaration of objects as well as functions. The basic difference when applied to objects is this:

  • const declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization.

  • constexpr declares an object as fit for use in what the Standard calls constant expressions. But note that constexpr is not the only way to do this.

When applied to functions the basic difference is this:

  • const can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members (except for mutable data members, which can be modified anyway).

  • constexpr can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly (†):

    • The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single return statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default and = delete are allowed, too, though.)
    • As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function: asm declaration, a goto statement, a statement with a label other than case and default, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed.
    • The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)

Constant expressions

As said above, constexpr declares both objects as well as functions as fit for use in constant expressions. A constant expression is more than merely constant:

  • It can be used in places that require compile-time evaluation, for example, template parameters and array-size specifiers:

      template<int N>
    class fixed_size_list
    { /*...*/ };

    fixed_size_list<X> mylist; // X must be an integer constant expression

    int numbers[X]; // X must be an integer constant expression
  • But note:

  • Declaring something as constexpr does not necessarily guarantee that it will be evaluated at compile time. It can be used for such, but it can be used in other places that are evaluated at run-time, as well.

  • An object may be fit for use in constant expressions without being declared constexpr. Example:

         int main()
    {
    const int N = 3;
    int numbers[N] = {1, 2, 3}; // N is constant expression
    }

    This is possible because N, being constant and initialized at declaration time with a literal, satisfies the criteria for a constant expression, even if it isn't declared constexpr.

So when do I actually have to use constexpr?

  • An object like N above can be used as constant expression without being declared constexpr. This is true for all objects that are:
  • const
  • of integral or enumeration type and
  • initialized at declaration time with an expression that is itself a constant expression


[This is due to §5.19/2: A constant expression must not include a subexpression that involves "an lvalue-to-rvalue modification unless […] a glvalue of integral or enumeration type […]" Thanks to Richard Smith for correcting my earlier claim that this was true for all literal types.]

  • For a function to be fit for use in constant expressions, it must be explicitly declared constexpr; it is not sufficient for it merely to satisfy the criteria for constant-expression functions. Example:

     template<int N>
    class list
    { };

    constexpr int sqr1(int arg)
    { return arg * arg; }

    int sqr2(int arg)
    { return arg * arg; }

    int main()
    {
    const int X = 2;
    list<sqr1(X)> mylist1; // OK: sqr1 is constexpr
    list<sqr2(X)> mylist2; // wrong: sqr2 is not constexpr
    }

When can I / should I use both, const and constexpr together?

A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr implies const.

constexpr const int N = 5;

is the same as

constexpr int N = 5;

However, note that there may be situations when the keywords each refer to different parts of the declaration:

static constexpr int N = 3;

int main()
{
constexpr const int *NP = &N;
}

Here, NP is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr and const are required: constexpr always refers to the expression being declared (here NP), while const refers to int (it declares a pointer-to-const). Removing the const would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N is in-fact a pointer-to-constant).

B. In member function declarations. In C++11, constexpr implies const, while in C++14 and C++17 that is not the case. A member function declared under C++11 as

constexpr void f();

needs to be declared as

constexpr void f() const;

under C++14 in order to still be usable as a const function.



Related Topics



Leave a reply



Submit