BDE 4.39.x Production Release
Loading...
Searching...
No Matches
bsls_consteval

Detailed Description

Provide macros related to compile-time evaluation.

Outline

Purpose

Provide macros related to compile-time evaluation.

Macros

See also
bsls_keyword

Description

This component provides preprocessor macros that, when possible, will identify whether a function is being evaluated at compile time. This enables branching to avoid the use of constructs that would not be valid to evaluate at compile time. When available, the std::is_constant_evaluated() function will be used. On some platforms where that is unavailable, a compiler intrinsic will be used instead. Finally, when no option for this functionality is available, the BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED macro will expand to false and BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE will not be defined. To ease writing declarations of functions where there is conditionally available compile-time behavior, BSLS_CONSTEVAL_CONSTEXPR is defined to be equivalent to constexpr if BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE is defined. Note that the BSLS_CONSTEVAL_CONSTEXPR macro is distinct from the BSLS_KEYWORD_CONSTEXPR* family in bsls_keyword : the former is defined according to whether the Standard Library function (or a suitable intrinsic replacement) is available, whereas the latter are defined according to which version of the C++ language is enabled.

Macro Reference

This section documents the preprocessor macros defined in this component.

BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED This macro expands to std::is_constant_evaluated() when it is available, or to a compiler intrinsic with equivalent functionality if available; otherwise it expands to false.

BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE This macro is defined to 1 if BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED can be used to identify compile-time evaluation, and is undefined otherwise.

BSLS_CONSTEVAL_CONSTEXPR This macro is defined to be constexpr if BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE is defined; otherwise it is defined as empty.

BSLS_CONSTEVAL_CONSTEXPR_MEMBER This macro is defined to be constexpr if BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE is defined; otherwise it is const. This should be applied to variables and member variables that should be initialized by a function that is BSLS_CONSTEVAL_CONSTEXPR.

Usage

This section illustrates intended use of this component.

Example 1: BSLS_CONSTEVAL_CONSTEXPR compute with output

In this simple example, the macros are used to determine when it is permissible to log a message to stdout.

/// Return `23` if the invocation is evaluated at compile time and that
/// is detectable, otherwise print a diagnostic message to `stdout` and
/// return `17`.
{
#ifdef BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE
return 23; // RETURN
}
#endif
puts("Computing value");
return 17; // RETURN
}
#define BSLS_CONSTEVAL_CONSTEXPR
Definition bsls_consteval.h:267
#define BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED
Definition bsls_consteval.h:260

Now we define a test1 function to invoke compute in different contexts. This function can be evaluated both at runtime and at compile time without errors. Below, this function is evaluated in both cases, and the difference in behavior is observed.

/// Invoke `compute` in both a const and non-const initialization,
/// verifying the expected results.
void test1()
{
int i = compute();
#ifdef BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE
ASSERT(17 == i);
ASSERT(23 == j);
#else
ASSERT(17 == i);
ASSERT(17 == j);
#endif
}
#define BSLS_CONSTEVAL_CONSTEXPR_MEMBER
Definition bsls_consteval.h:268

When 17 == i or 17 == j, compute will write "Computing value\n" to stdout, so this message will be written once or twice depending on whether BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE is defined. The variable j will always be const, and on platforms where the compute function supports being constexpr, i.e. those where it can use BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED to suppress its use of standard output, j will also be constexpr and its value will be computed at compile time.

Example 2: Evolving a constexpr function

Consider the situation where there are two viable implementations of the same algorithm, one of which satisfies the (stringent and pessimizing) requirements needed to be a constexpr function, the other of which takes advantage of runtime optimizations (such as hardware acceleration, exceptions, or non-constexpr third-party libraries) that are not available at compile time on any platform:

int runtimeCompute(int input);
/// Return a complicated computed value based on the specified `input`.
BSLS_KEYWORD_CONSTEXPR int compiletimeCompute(int input);
#define BSLS_KEYWORD_CONSTEXPR
Definition bsls_keyword.h:624

Assuming these functions were introduced long ago, it is likely they are heavily used wherever valid throughout a codebase. The compiletimeCompute function is likely used to initialize many const and constexpr variables, but also potentially used in many runtime-only expressions, or as part of other constexpr functions that are themselves sometimes used at runtime. The runtimeCompute function, similarly, is likely used in many contexts that could become constexpr or be evaluated at compile time, but is hindered due to itself not being constexpr.

We can begin to transform compiletimeCompute and runtimeCompute to both have improved performance wherever they might be used by moving their implementations to separate functions:

/// Return a complicated computed value based on the specified `input`.
int runtimeComputeImpl(int input);
/// Return a complicated computed value based on the specified `input`.
BSLS_KEYWORD_CONSTEXPR int compiletimeComputeImpl(int input);

Then, for compiletimeCompute we can provide a new implementation that will use the better runtime algorithm when possible, while remaining constexpr on all of the platforms where it previously was constexpr (i.e., without changing its declaration):

/// Return a complicated computed value based on the specified `input`.
BSLS_KEYWORD_CONSTEXPR int compiletimeCompute(int input)
{
#ifdef BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE
return runtimeComputeImpl(input); // RETURN
}
#endif // BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE
return compiletimeComputeImpl(input);
}

Now clients using compiletimeCompute at runtime, both within and outside of other constexpr functions, will get the benefits of an improved algorithm without any need for change.

Similarly, runtimeCompute can become opportunistically constexpr by declaring the function with BSLS_CONSTEVAL_CONSTEXPR, which might promote some existing evaluations from runtime to compile time on platforms that can support it:

/// Return a complicated computed value based on the specified `input`.
BSLS_CONSTEVAL_CONSTEXPR int runtimeCompute(int input)
{
#ifdef BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE
return compiletimeComputeImpl(input); // RETURN
}
#endif // BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED_IS_ACTIVE
return runtimeComputeImpl(input);
}

Clients of runtimeCompute can continue to use it at runtime with no changes, occasionally getting the benefits of the compile-time algorithm. When using runtimeCompute to initialize a variable, the compile-time behavior can be forced by annotating the variable with BSLS_CONSTEVAL_CONSTEXPR_MEMBER.

With these changes, clients on older platforms can continue to take advantage of having optimal algorithms available at both compile time and runtime while getting the best available implementation on newer platforms that enable the detection of compile-time evaluation.

Macro Definition Documentation

◆ BSLS_CONSTEVAL_CONSTEXPR

#define BSLS_CONSTEVAL_CONSTEXPR

◆ BSLS_CONSTEVAL_CONSTEXPR_MEMBER

#define BSLS_CONSTEVAL_CONSTEXPR_MEMBER   const

◆ BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED

#define BSLS_CONSTEVAL_IS_CONSTANT_EVALUATED   false