Why How to Call a Non-Constexpr Function Inside a Constexpr Function

calling non constexpr function from constexpr allowed in some conditions

A constexpr function implies that it is possible to evaluate the value of the function at compile time. Since this is possible for the input true the function is a valid constexpr. Remember that a constexpr function can have an address just as a regular function, it does not need to be compile time, only when used as a compile time function (which you do not in your example).

As mentioned on the constexpr page on cppreference:

A constexpr function must satisfy the following requirements:

  • it must not be virtual
  • its return type must be LiteralType
  • each of its parameters must be LiteralType
  • 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 (for constructors, use in a constant initializer is sufficient) (since C++14). No diagnostic is required for a violation of this bullet. (Emphasis mine)

Your function fulfils all of the above requirements: it is not virtual, it returns a literal type, the parameter is literal. And more interestingly last bullet point: there exists at least one set of argument values for which the function is actually fully compile time. (hence my emphasis of the last bullet)

As W.F. mentioned in the comments, the function can be used compile time, but only for valid inputs, that is, inputs that does not lead to a sub expression that is not compile time constant. So the input true will work, but false will not since it will lead to bla being evaluated.

This is also stated in the standard: §10.1.5.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.

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

constexpr int f()
{ return f(true); } // ill-formed, no diagnostic required

See the examples from the standard document in particular.

C++ error: Call to non-constexpr function

Streaming to std::cout is not allowed in a constexpr function context. In fact, only a limited set of things are. Read the cppreference article on constexpr.

constexpr result from non-constexpr call

As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.

There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.

The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).

Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.

Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.

Note that, as mentioned above, the rules are stricter if you use references, e.g.

A a;
A& ar = a;
constexpr int kInt = f(ar);

will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)

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.

non-constexpr calls in constexpr functions

It is not surprising that this example does not work on gcc, according to this page C++14s generalized constexpr functions are not yet supported.

I believe the source code in your edit is valid C++14, the Draft Standard available here contains, on page 126(§5.19) an example that is very similiar to yours(without template, non member functions and they use a temporary):

constexpr int incr(int &n) {
return ++n;
}

constexpr int g(int k) {
constexpr int x = incr(k);// error: incr(k) is not a core constant
// expression because lifetime of k
// began outside the expression incr(k)
return x;
}

constexpr int h(int k) {
int x = incr(k);
// OK: incr(k) is not required to be a core
// constant expression
return x;
}

constexpr int y = h(1); // OK: initializes y with the value 2
// h(1) is a core constant expression because
// the lifetime of k begins inside h(1)

If my reading of the Standard is correct, the fact that yours are member functions should not matter, because "this" is not evaluated and it seems like the code does not violate any of the other rules outlaid in §5.19(2).

Call non constexpr from constexpr template function

It's because foo is a function template and bar is a function.

For a function (e.g. bar) to be constexpr it must meet all of the constexpr rules (which change from standard to standard) and that is checked at the definition of the function. You get an error if those rules aren't met.

For a function template because you have only a template to generate functions you can't enforce the rules for constexpr. E.g. in your example at the point of the template definition you don't know if set<T>(x) is constexpr because you might have some template instantiations of set who are constexpr and some other template instantiations for set which are not. So you can't check that foo meets the requirements for constexpr. You can only check specific instantiations of foo if are constexpr e.g. foo<int> or foo<char> etc.

C++ handles this situation by allowing constexpr for a function template indiscriminately (sort of). However if a instantiations of the template doesn't meet the requirements for constexpr then that is allowed, but the specialization is not allowed in a a constant expression.

You can see this with a slightly modified code from your example:

auto set(int a) { return a; }
constexpr auto set(char a) { return a; }

template<class T>
constexpr auto foo(T x){
return set(x);
}

auto test()
{
auto x = foo(24); // foo<int> OK, no error
//constexpr auto cx = foo(24) // foo<int> compiler error

auto y = foo('a'); // foo<char> OK, no erro
constexpr auto y = foo('a'); // foo<char> OK
}

§7.1.5 [dcl.constexpr]


  1. If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy
    the requirements for a constexpr function or constexpr constructor,
    that specialization is still a constexpr function or constexpr
    constructor, even though a call to such a function cannot appear in a
    constant expression. If no specialization of the template would
    satisfy the requirements for a constexpr function or constexpr
    constructor when considered as a non-template function or constructor,
    the template is ill-formed; no diagnostic required.

Non-constexpr variant member call compiling inside constexpr class member function with condition - why?

You don't actually need to delve much into std::variant to reason about this. This is mostly about how constant expressions work. constexpr functions must be defined in a way that allows for evaluation in a constant expression. It doesn't matter if for some arguments we run into something that can't appear in a constant expression, so long as for other arguments we obtain a valid constant expression. This is mentioned explicitly in the standard, with an exeample

[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, or, for a
constructor, a constant initializer for some object
([basic.start.static]), the program is ill-formed, no diagnostic
required. [ Example:

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

struct B {
constexpr B(int x) : i(0) { } // x is unused
int i;
};

int global;

struct D : B {
constexpr D() : B(global) { } // ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};

 — end example ]

See how f(bool) is a valid constexpr function? Even though a throw expression may not be evaluated in a constant expression, it can still appear in a constexpr function. It's no problem so long as constant evaluation doesn't reach it.

If there is no set of arguments for which a constexpr function can be used in a constant expression, the program is ill-formed. No diagnostic is required for this sort of ill-formed program because checking this condition from the function definition alone is intractable in general. Nevertheless, it's invalid C++, even if the compiler raises no error. But for some cases, it can be checked, and so a compiler could be obliged raise a diagnostic.

Your f without a condition falls into this category of ill-formed constructs. No matter how f is called, its execution will result in invoking emplace, which cannot appear in a constant expression. But it's easy enough to detect, so your compiler tells you it's a problem.

Your second version, with the condition, no longer invokes emplace unconditionally. Now its conditional. The condition itself is relying on a constexpr function, so it's not immediately ill-formed. Everything would depend on the arguments to the function (this included). So it doesn't raise an error immediately.

Non-constexpr variable sometimes usable in a constexpr context?

In particular, how is this formalized?

http://eel.is/c++draft/expr.const#4.1

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:

this ([expr.prim.this]), except in a constexpr function ([dcl.constexpr]) that is being evaluated as part of e;

Access to non-static members evaluates the this pointer. Access to a static member does not.

Using a `constexpr` function on a variable passed as lvalue in a non-constexpr function

Below, I answer as to why your code doesn't work. Focusing on your use-case: as the others have said, std::array::size is not static, and all std::size does is call that non-static function. Your best bet is to simply add a static size function to your Vector class:

static constexpr auto size() {
return D;
}

Your implementation of cross will not work because you cannot use non-constant expressions to initialize templates. See this SO answer on why function arguments are not constant expressions.

Basically, calling your cross function requires generating a new instance of the cross_dispatch structure for every different value of std::size(vec1), which also requires knowing the address of every given vec1 at compile-time since std::size calls a non-static function. You should be able to see from this that the compiler simply can't know which instances of cross_dispatch need to be created.

Above, I provided a solution specific to your use-case. If you were doing more than measuring the size of your Vector, a second solution would involve passing the objects as template parameters instead (which will require them to be static):

template <typename VectorType, VectorType const& vec1, VectorType const& vec2>
constexpr VectorType cross()
{
return cross_dispatch<std::size(vec1), VectorType>::apply(vec1, vec2);
}

int main()
{
static vector p1 {1.,2.,3.};
static vector q1 {1.,2.,3.};

cross<vector, p1, q1>();
}

Because p1 and q1 are static, their addresses can be known at compile-time, allowing cross's template to be initialized. Template parameters do not change at run-time, so std::size(vec1) is now a constant expression.

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).



Related Topics



Leave a reply



Submit