holdback

holdback

part of shnell – a source to source compiler enhancement tool

© Jens Gustedt, 2022

Define a holdback for a lambda expression

This enables the definition of holdbacks, that are macro definitions of lambda expressions that are expanded differently if they are encountered with or without arguments.

With arguments

If encountered in a macro call with arguments the expansion first squeezes all evaluations of the arguments into evaluations that are captured with value captures and then defines local variables that mimic the parameters.

To mimic the parameters declarations of array parameters, these are adjusted to definition of pointer variables. This only works if the array definition is not hidden inside a typedef; if so this results in a compilation error.

Type generic lambdas

If these parameters have auto types, this lets you define calls to type-generic lambdas, even if your compiler doesn’t support these.

Constant expressions

If all arguments are constant expressions and the function call doesn’t refer to global variables the result can also be a constant expression. (This works for C++ lambdas out of the box, otherwise you’d have to see for the gcc extension described below.)

Support on pre-C23 compilers

If your platform does not support lambda expressions, yet, you may indicate that by setting the feature test macro NO_LAMBDAS. Then, if possible, a version that only uses gcc’s compound expressions, ({ }), is used. If you use that feature you’d have to have at most one return statement in the body and that statement must be the very last. If none of this works, a normal function call (see below) is used.

Without arguments

If such a holdback is encountered otherwise, it is just replaced by the lambda expression as specified. This can be used for compilers that support type-generic lambdas expression only in contexts where they are converted to a function pointer with a known prototype.

Note that if the language is C++, for the latter the lambda expression is modified such that parameter declarations such as double a[const static 78] are adjusted to the pointer definition of the parameter object, so here double (*const a). We do this because that notation is currently not valid in C++.

If NO_LAMBDAS is defined, a static inline function is generated, instead. This has its limits and only works if the capture list is in fact empty. For the generated function the same adjustment of array parameters as described above is made if the target language is C++.

Extensions

As other extensions we also recognize __restrict__, typeof and __typeof__.

Compiler prefix

The shnell distribution also has a compiler prefix holdback that uses the dialect HOLDBACK to apply holdups systematically to your code.

Example:

#pragma CMOD amend holdback max
 [](auto a, auto b) [[unsequenced]] {
     return (a < 0)
       ? (b < 0) ? (a < b) ? b : a : b
       : (0 < b) ? (b < a) ? a : b : a;
}
#pragma CMOD done

A call max(23U, -21) would expand to something like

(
 [cap1 = (23U), cap2 = (-21)](void) [[unsequenced]] {
   auto a = cap1; /* is unsigned */
   auto b = cap2; /* is signed */
   return (a < 0)
     ? (b < 0) ? (a < b) ? b : a : b
     : (0 < b) ? (b < a) ? a : b : a; /* result is unsigned */
 }()
)

Note that this lambda expression now has no parameters at all, in particular none that would be auto. Also, note that besides the initializers of captures and pseudo-parameters, this modified lambda still has only one return statement that evaluates an expression. This is close to what historical versions of C++ accepted as a constexpr functions or lambda expressions.

If the macro max then is used otherwise in the program (that is, not within a macro call) it is just replaced by the orginal lambda expression. If your compiler supports conversion of type-generic lambdas to function pointers, the following line

unsigned (*maxUI)(unsigned, signed) [[unsequenced]] = max;

would be replaced by

unsigned (*maxUI)(unsigned, signed)  [[unsequenced]] = [](auto a, auto b)  [[unsequenced]] {
 return (a < 0)
   ? (b < 0) ? (a < b) ? b : a : b
   : (0 < b) ? (b < a) ? a : b : a;
};

Coding and configuration

The following code is needed to enable the sh-module framework.

SRC="$_" . "${0%%/${0##*/}}/import.sh"

Imports

The following sh-modules are imported:

Details