eĿlipsis
a language independent preprocessor
 
Loading...
Searching...
No Matches
ellipsis-loc.h File Reference

Provide locally scoped identifers. More...

Go to the source code of this file.

Macros

#define __LOC(POS0, POS1)
 refer to an identifier created with __LOC_NEW
 
#define __LOC_NEW
 create a new identifier in the current scope of {}
 

Detailed Description

Provide locally scoped identifers.

This provides two macros __LOC and __LOC_NEW that expand and manage identifiers that are unique within the current set of curly braces {}. (For using another prefix than __LOC, see below.)

double __LOC_NEW = 5;
double __LOC_NEW = 6;
printf("%g %g\n", __LOC(), __LOC(1));
#define __LOC_NEW
create a new identifier in the current scope of {}
Definition ellipsis-loc.h:3
#define __LOC(POS0, POS1)
refer to an identifier created with __LOC_NEW
Definition ellipsis-loc.h:2

Note that here __LOC needs parenthesis for the invokation and receives 0, 1, or 2 arguments. The above expands to something similar as the following

double __LOC_ID_1_9 = 5;
double __LOC_ID_1_10 = 6;
printf("%g %g\n", __LOC_ID_1_10, __LOC_ID_1_9);

Obivously here the concrete names that are chosen depend on the context where this code would be found. You should not try to recover such names manually.

The first argument to __LOC indicates to which of the unique idenfier it refers: 0, the default, refers to the last identifer created with __LOC_NEW (here the variable initialized with the value 6), 1 to the one before (here the variable initialized with the value 5).

So the above example should print the values 6 5 in that order.

Curly braces add a hierarchical aspect, namely nesting levels for identifiers. To see that consider a macro SWAPEM(A, B) with the following expansion:

do {
register auto const __LOC_NEW = &(A);
register auto const __LOC_NEW = &(B);
register auto const __LOC_NEW = *__LOC(2);
*__LOC(2) = *__LOC(1);
*__LOC(1) = __LOC();
} while(false)

Here { moves one "level" up in the hierarchy of identifiers. To see that in context let's amend our example from above by adding an invokation of SWAPEM.

double __LOC_NEW = 5;
double __LOC_NEW = 6;
SWAPEM(__LOC(), __LOC(1));
printf("%g %g\n", __LOC(), __LOC(1));

The expansion would be looking similar to the following:

double __LOC_ID_1_9 = 5;
double __LOC_ID_1_10 = 6;
do { register auto const __LOC_ID_2_7 = &(__LOC_ID_1_10); register auto const __LOC_ID_2_8 = &(__LOC_ID_1_9); register auto const __LOC_ID_2_9 = *__LOC_ID_2_7; *__LOC_ID_2_7= *__LOC_ID_2_8; *__LOC_ID_2_8 = __LOC_ID_2_9; } while(false);
printf("%g %g\n", __LOC_ID_1_10, __LOC_ID_1_9);

So here the expansion of the three lines that we had before has not changed at all; in the printf call we can easily refer to the identifiers as we did before without needing to worry about what effects the invokation of SWAPEM might have caused.

In contrast to that the call to SWAPEM itself sees a new set of names for its variables, here identifiers starting with __LOC_ID_2_, and so the variables defined there are in no conflict with the identifiers in the surrounding scope.

As a result of this invokation, the contents of the two variables is swapped and the two values would be 5 6, inverted compared to what we had seen above.

__LOC has an optional second parameter that refers to the level in which the identifier is sought. As for the first paremeter, 0, the default, refers to the current level, 1 refers to one level down and so on.

Typically you would include this feature with a directive such as

#include_directives <ellipsis-loc.h>

This provides you with a set of macros as described above that have names starting with __LOC.

See also
LOC_NAME

Macro Definition Documentation

◆ __LOC

#define __LOC (   POS0,
  POS1 
)

refer to an identifier created with __LOC_NEW

Parameters
POS0is the "distance" to the invocation of __LOC_NEW: 0 refers to the latest, 1 to the one before and so on.
POS1refers to the level of curly braces of the referred identifier, 0 is the current level, 1 is one level up and so on.

Let's see this with an example:

inline void swapem(double* __LOC_NEW, double* __LOC_NEW) {
double const __LOC_NEW = __LOC(1, 1);
*__LOC(1, 1) = *__LOC(0, 1);
*__LOC(0, 1) = *__LOC(0, 0);
}

Here, we create three variables, but in different scopes. Inside the function body, the two parameters are referred by __LOC(1, 1) and __LOC(0, 1) because they are the before last and last defined names in the scope outside the {} of the function body (therefore the second argument 1 for both). The variable declared inside the body is referred by __LOC(0, 0) (or __LOC(0) or __LOC()), with a 0 as second parameter referring to the current level of {}.

The above example will be replaced by something like the following.

inline void swapem(double* __LOC_ID_0_3, double* __LOC_ID_0_4) {
double const __LOC_ID_1_5 = *__LOC_ID_0_3;
*__LOC_ID_0_3 = *__LOC_ID_0_4;
*__LOC_ID_0_4 = __LOC_ID_1_5;
}

All three identifiers will be unique in the whole translation unit, and thus will not conflict with anything else.

Another use case for __LOC_NEW would be parameters in function prototypes, for which you want to avoid that they ever conflict with user defined identifiers.

void copyem(size_t __LOC_NEW, double [static restrict __LOC()], double const [static restrict __LOC()]);

Not that here the second an third parameters have no names; we don't need them so we omit them. In contrast to that the first parameter conveys information about the type of the two others and is used for their definition. After expansion this looks similar to the following

void copyem(size_t __LOC_ID_0_5, double [static restrict __LOC_ID_0_5], double const [static restrict __LOC_ID_0_5]);

using an identifier that is guaranteed not to be in conflict with any other identifier of the translation unit and that we just don't want to have to invent ourselves.

◆ __LOC_NEW

#define __LOC_NEW

create a new identifier in the current scope of {}

See also
__LOC for how to refer to such an identifier
Warning
Don't use that feature to create names with external linkage. Otherwise, this could create name conflicts during linking with other units.
Don't use that feature to create names for elements of struct or union types that you provide in headers, eve of those that you want to hide from the user. These would get instantiated differently in different translation units, and thus types would not be compatible across translation units.