Skip to content

Instantly share code, notes, and snippets.

@markusbkk
Forked from CMCDragonkai/README.md
Created October 21, 2024 12:52
Show Gist options
  • Select an option

  • Save markusbkk/a6654405018e8b5910d015527403d6e5 to your computer and use it in GitHub Desktop.

Select an option

Save markusbkk/a6654405018e8b5910d015527403d6e5 to your computer and use it in GitHub Desktop.
C: Namespaces using Macros (Namespace Macro Pattern)

Namespace Macro Pattern

Compilation of this example:

gcc -c -D NAMESPACE=First_ first.c -o First_first.o
gcc -c -D NAMESPACE=Second_ second.c -o Second_second.o
gcc -c -D NAMESPACE=Third_ third.c -o Third_third.o
gcc -c -D NAMESPACE=Fourth_ fourth.c -o Fourth_fourth.o
gcc -c -D NAMESPACE=FirstAgain_ first.c -o FirstAgain_first.o
gcc -c -D NAMESPACE=SecondAgain_ second.c -o SecondAgain_second.o
gcc -c main.c -o main.o
gcc main.o First_first.o Second_second.o Third_third.o Fourth_fourth.o FirstAgain_first.o SecondAgain_second.o -o main.exe

There are limitations with this namespacing macro pattern!

Firstly we rely on #pragma once for our headers. This is because inclusion guard macros cannot be namespaced. Macro identifiers are always literal, it is impossible to define a macro identifier from an expression. Thus using inclusion guards would result in namespace pollution, which is what we're trying to avoid with this namespacing pattern. If we allowed inclusion guards, that would defeat the point of using this namespacing pattern! Note that #pragma once relies on file characteristics and file path, whereas inclusion guards rely on a unique macro constant.

Ideally we would have preferred using something like:

#ifndef NS(FILE_H)
#define eval(NS(FILE_H))
// ...
#endif

We need something to disambiguate #define NS(FILE_H), does it mean to define NS() macro expression again, or does it mean to evaluate NS(FILE_H) and use the resulting value as the new macro identifier?

The #pragma once has wide support: https://en.wikipedia.org/wiki/Pragma_once#Portability

Beware there are big caveats with #pragma once:

With or without #pragma once in our header files, header files that include included header files can result in namespace conflicts. The namespace conflicts occur in opposite ways.

With #pragma once, when a header file that includes an included header file, and sets a different namespace from the prior inclusion for the current inclusion, a compilation failure can occur because the current inclusion will be not be included. The solution is to make all inclusions of a particular header file use the same namespace.

Without #pragma once, when a header file that includes an included header file, and sets the same namespace for the current inclusion that was used in the prior inclusion, a compilation failure can occur due to multiple definitions caused by multiple inclusion. The solution is to make all inclusions of a particular header file use different namespaces.

Out of the 2 above situations, which is the more desirable? I'd argue the former should be preferred from the latter. The latter forces you compile a separate namespaced object file for linking, for every repeated inclusion. This is pretty inefficient! The former situation allows empty namespaced libraries to be included normally without any problems. Only once you meet a namespace clash with a empty namespace, do you consider adding a custom namespace. At this point you just need to remember that this custom namespace should be applied to every inclusion of the same library.

So just remember, don't namespace (just means leave the namespace empty) until you need it. It will reduce headaches under repeated nested inclusion.

Note that the above mentioned problem does not apply to separate compilation of object files. Each object file can be compiled from the same libraries with different or the same namespace without any problems. They are later just linked together. However for efficiency reasons, it may be better for all the linked objects to standardise on the namespace.

Secondly we rely on #pragma push_macro and #pragma pop_macro for libraries that need their own namespace while namespacing their own dependencies. This is required because the preprocessor doesn't perform imperatively. See:

Ideally we would have preferred using "temporary macro variables":

#define THIS_NAMESPACE NAMESPACE
// ...
#define NAMESPACE THIS_NAMESPACE

Which should be the equivalent of:

var x = y;
y = x;

But it doesn't work because the C preprocessor is globally declarative, not imperative.

The #pragma push_macro and #pragma pop_macro only works in MSVC, GCC and Clang.

#include "./first.h"
#include "./namespace.h"
int NS(addone) (int x) {
return x + 1;
}
#pragma once
#include "./namespace.h"
int NS(addone) (int);
#include "./namespace_undef.h"
#include "./fourth.h"
#pragma push_macro("NAMESPACE")
#undef NAMESPACE
#define NAMESPACE FirstAgain_
#include "./first.h"
#undef NAMESPACE
#define NAMESPACE SecondAgain_
#include "./second.h"
#undef NAMESPACE
#pragma pop_macro("NAMESPACE")
#include "./namespace.h"
int NS(addfourth) (int x) {
return FirstAgain_addone(x) + SecondAgain_addone(x);
}
#pragma once
#include "./namespace.h"
int NS(addfourth) (int);
#include "./namespace_undef.h"
#define NAMESPACE First_
#include "./first.h"
#undef NAMESPACE
#define NAMESPACE Second_
#include "./second.h"
#undef NAMESPACE
#define NAMESPACE Third_
#include "./third.h"
#undef NAMESPACE
#define NAMESPACE Fourth_
#include "./fourth.h"
#undef NAMESPACE
int main () {
return First_addone(1) + Second_addone(2) + Third_addboth(3) + Fourth_addfourth(4);
}
// Results: 2 + 3 + (4 + 4) + (5 + 5) = 23
#define CONC(A, B) CONC_(A, B)
#define CONC_(A, B) A##B
#ifndef NAMESPACE
#define NAMESPACE
#endif
#define NS(name) CONC(NAMESPACE, name)
#undef NS
#undef CONC_
#undef CONC
// No `#undef NAMESPACE` because the corresponding library needs `NAMESPACE`.
// The consumer will `#define NAMESPACE` and `#undef NAMESPACE` as necessary.
#include "./second.h"
#include "./namespace.h"
int NS(addone) (int x) {
return x + 1;
}
#pragma once
#include "./namespace.h"
int NS(addone) (int);
#include "./namespace_undef.h"
#include "./third.h"
#pragma push_macro("NAMESPACE")
#undef NAMESPACE
#define NAMESPACE FirstAgain_
#include "./first.h"
#undef NAMESPACE
#define NAMESPACE SecondAgain_
#include "./second.h"
#undef NAMESPACE
#pragma pop_macro("NAMESPACE")
#include "./namespace.h"
int NS(addboth) (int x) {
return FirstAgain_addone(x) + SecondAgain_addone(x);
}
#pragma once
#include "./namespace.h"
int NS(addboth) (int);
#include "./namespace_undef.h"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment