Showing posts with label cpp_operator. Show all posts
Showing posts with label cpp_operator. Show all posts

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.

Feb 24, 2022

[kernnel][C][C++] ternary conditional operator trick in action

Reference:
Return type of '?:' (ternary conditional operator): https://stackoverflow.com/questions/8535226/return-type-of-ternary-conditional-operator

__is_constexpr() macro is dark magic: https://lore.kernel.org/linux-hardening/20220131204357.1133674-1-keescook@chromium.org/?fbclid=IwAR0Rgg_tGDk0qiEyuDsuZwERITSdstxmU2-bOtadb7iOOCtw3tgOPKEQ5hE

Using ternary conditional operator to get the type we want for a template type is often used in C++.

Here the same idea applies in C macro:

#define __is_constexpr(x) \
	(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
Details:
 - sizeof() is an integer constant expression, and does not evaluate the
   value of its operand; it only examines the type of its operand.
 - The results of comparing two integer constant expressions is also
   an integer constant expression.
 - The use of literal "8" is to avoid warnings about unaligned pointers;
   these could otherwise just be "1"s.
 - (long)(x) is used to avoid warnings about 64-bit types on 32-bit
   architectures.
 - The C standard defines an "integer constant expression" as different
   from a "null pointer constant" (an integer constant 0 pointer).
 - The conditional operator ("... ? ... : ...") returns the type of the
   operand that isn't a null pointer constant. This behavior is the
   central mechanism of the macro.
 - If (x) is an integer constant expression, then the "* 0l" resolves it
   into a null pointer constant, which forces the conditional operator
   to return the type of the last operand: "(int *)".
 - If (x) is not an integer constant expression, then the type of the
   conditional operator is from the first operand: "(void *)".
 - sizeof(int) == 4 and sizeof(void) == 1.
 - The ultimate comparison to "sizeof(int)" chooses between either:
     sizeof(*((int *) (8)) == sizeof(int)   (x was a constant expression)
     sizeof(*((void *)(8)) == sizeof(void)  (x was not a constant expression)


For a conditional expression (?:) to be an lvalue, the second and third operands must be lvalues of the same type.
This is because the type and value category of a conditional expression is determined at compile time and must be appropriate whether or not the condition is true.
If one of the operands must be converted to a different type to match the other than the conditional expression cannot be an lvalue as the result of this conversion would not be an lvalue (but a r-value).

Thus:

OK:
int x = 1;
int y = 2;
(x > y ? x : y) = 100; // l-value on the left side of =

Not OK:
int x = 1;
long y = 2;
(x > y ? x : y) = 100; // type conversion to long as r-value type