Can we not "normalize" the behavior of a constexpr function by using is_constant_evaluated ? I understand why the successful cases work, but can't wrap my head around why the unsuccessful one doesn't.
gcc12.2 with -std=c 20 flag
error: the value of ‘str1’ is not usable in a constant expression
constexpr auto numArgs = count(FMT); \
#include <memory>
#include <iostream>
#include <string.h>
template<size_t arrSize>
void print()
{
std::cout << "Number of format characters: " << arrSize << "\n";
}
constexpr size_t countFormat(const char* format)
{
if(format[0] == '\0')
return 0;
return (format[0] == '%' ? 1u : 0u) countFormat(format 1);
}
constexpr size_t count(const char* format)
{
return std::is_constant_evaluated() ? countFormat( format ) : 0;
}
#define LOGMSG(FMT) { \
constexpr auto numArgs = count(FMT); \
print<numArgs>(); \
}
int main()
{
const auto str1 = "Test %d %s";
constexpr auto str2 = "Test %d %s";
LOGMSG(str1);
LOGMSG(str2);
LOGMSG("Test %d %s");
return 0;
}
CodePudding user response:
std::is_constant_evaluated
is not a (magic) escape hatch when constant evaluation fails; generally it just tells you about the context in which you were called, with no ability to influence that context. (There is an exception for const
variables that can be “promoted” to constexpr
for C 03 compatibility, but that exception doesn’t do what you want and it does hurt everyone’s head, so let’s not.) It is possible to SFINAE on whether something is a constant expression, but only based on inputs that definitely are.
In general, constexpr programming doesn’t make C into an interpreted language; it’s a feature that it usually produces the same result as runtime evaluation, and the cases where it doesn’t are quite limited.
Moreover, constant evaluation has already failed in trying to call count
, since it involves reading a (pointer) variable that isn’t available. (It might be possible to work around this issue by using references, but that doesn’t address the fundamental impossibility here.)
CodePudding user response:
My goal is to make the function work even if the parameter is not known at compile time.
If constexpr
function is evaluated in constant expression doesn't depend of its parameter (known or not at compile time) but from the context where the function is called.
in your case:
constexpr auto numArgs = count(FMT);
count
has to be evaluated as constant expression (so std::is_constant_evaluated()
would be true
).
When FMT
is not known at compile time, then you will have error.
Your count
implementation just "break" the runtime computation and doesn't allow the function to become constexpr
when argument is not known at compile time.
You cannot branch depending of that argument "property".
Maybe you want overload on type constructed with consteval
:
struct string_literal
{
consteval string_literal(const char* s) : s(s) {}
const char* s;
};
consteval size_t countFormat(string_literal format)
{
if(format.s[0] == '\0')
return 0;
return (format.s[0] == '%' ? 1u : 0u) countFormat(format.s 1);
}
consteval size_t count(string_literal format)
{
return countFormat(format);
}
constexpr size_t count(const char*const&)
{
return 0;
}
Demo.