Jun 11, 2022

[C++][C++20] consteval / constexpr





Within compile-time function, there is a difference between static typing of the code and dynamic computing of values.
consteval void process() {
    constexpr std::array a1{0, 8, 15};
    constexpr auto n1 = std::ranges::count(a1, 0); // OK
    std::array<int, n1> a1b; // OK

    std::array a2{0, 8, 15};
    // constexpr auto n2 = std::ranges::count(a2, 0); // ERROR

    std::array a3{0, 8, 15};
    auto n3 = std::ranges::count(a3, 0);
    // std::array<int, n3> a3b; // ERROR
}



std::is_constant_evaluated
Yields true when it called in a manifestly constant-evaluated expression or conversion.
That is roughly the case if we call it:
  • in a constant-expression, or
  • in a constant context (in 'if constexpr', a 'consteval function', or an 'constant initialization')
  • for an initializer of a variable usable at compile time
#include <type_traits>
#include <cstring>

constexpr int len(const char* s) {
	if (std::is_constant_evaluated()) {
    	// compile-time friendly code
		int idx = 0;
		while (s[idx] != '\0') { 			
			++idx;
		}
		return idx;
	} else {
		return std::strlen(s); // function called at runtime
	}
}
constexpr bool isConstEval() {
    return std::is_constant_evaluated();
}

bool g1 = isConstEval(); // true
const bool g2 = isConstEval(); // true
static bool g3 = isConstEval(); // true
static int g4 = g1 + isConstEval(); // false

int main() {
    bool l1 = isConstEval(); // false
    const bool l2 = isConstEval(); // true
    static bool l3 = isConstEval(); // true
    int l4 = g1 + isConstEval(); // false
    const int l5 = g1 + isConstEval(); // false
    static int l6 = g1 + isConstEval(); // false
    int l7 = isConstEval() + isConstEval(); // false
    const auto l8 = isConstEval() + 42 + isConstEval(); // true
}
bool runtimeFunc() {
	return std::is_constant_evaluated(); // always false
}
constexpr bool constexprFunc() {
	return std::is_constant_evaluated(); // may be false or true
}
consteval bool constevalFunc() {
	return std::is_constant_evaluated(); // always true
}
void foo() {
    bool b1 = runtimeFunc(); // F
    bool b2 = constexprFunc(); // F
    bool b3 = constevalFunc(); // T

    static bool sb1 = runtimeFunc(); // F
    static bool sb2 = constexprFunc(); // T
    static bool ab3 = constevalFunc(); // T

    const bool cb1 = runtimeFunc(); // F
    const bool cb2 = constexprFunc(); // T
    const bool cb3 = constevalFunc(); // T

    int y = 42;
    static bool sb4 = y + runtimeFunc(); // F
    static bool sb5 = y + constexprFunc(); // F
    static bool ab6 = y + constevalFunc(); // T

    const bool cb4 = y + runtimeFunc(); // F
    const bool cb5 = y + constexprFunc(); // F
    const bool cb6 = y + constevalFunc(); // T
}

When it makes no sense to use std::is_constant_evaluated()

As condition in a compile-time if, because that always yields true:
if constexpr (std::is_constant_evaluated()) {
        // always true
    }

Inside a pure runtime function, because that usually yields false.
The only exception is if it is used in a local constant evaluation:
void foo() {
    if (std::is_constant_evaluated()) {
        // always false
    }
    const bool b = std::is_constant_evaluated(); // true
}

Inside a consteval function, because that always yields true:
consteval void foo() {
    if (std::is_constant_evaluated()) { // always true
    }
}

Therefore, using std::is_constant_evaluated() usually only makes sense in constexpr functions.

std::is_constant_evaluated() and Operator ?: (conditional (ternary) operator)

int sz = 10;
constexpr bool sz1 = std::is_constant_evaluated() ? 20 : sz; // true, so 20
constexpr bool sz2 = std::is_constant_evaluated() ? sz : 20; // false, so ERROR

Reasoning as follows:
  • The initialization of sz1 and sz2 is either static initialization or dynamic initialization.
  • For static initialization, the initializer must be constant. So, the compiler attempts to evaluate the initializer with std::is_constant_evaluated() treated as a constant of value true.
  • Therefore, the previous result is discarded and the initializer is evaluated with std::is_constant_evaluated() producing false instead. So, the expression to initialize sz2 is also 20.
  • However, sz2 is not necessarily a constant, because std::is_constant_evaluated() is not necessarily a constant-expression during this evaluation. So, the initialization of sz2 with this 20 does not compile.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.