Compile-time disabled code with C++20 modules?

131 Views Asked by At

I was working on a small example program, as a prototype proof-of-concept:

// disable_lib.cpp
export module disable_lib;
import <iostream>;

extern bool bDoStuff;

export void do_stuff()
{
    if (bDoStuff) { std::cout << "stuff" << std::endl; }
}

This module compiles with with this command:

g++-13 -std=c++23 -fmodules-ts -ggdb -c disable_lib.cpp -o disable_lib.o

Naturally, I tried to declare bDoStuff as extern, because I want its definition specified in another translation unit, which could look like this:

import disable_lib;
bDoStuff = false;

int main()
{
    do_stuff(); // <-- here, we should not print "stuff" !
    return 0;
}

When I try to compile the main file, I get relocation errors:

/usr/bin/ld: disable_lib.o: warning: relocation against `_ZW11disable_lib8bDoStuff' in read-only section `.text'
/usr/bin/ld: disable_lib.o: in function `do_stuff@disable_lib()':
/home/gg/my-repos/modules-presentation/code/disable_lib.cpp:13: undefined reference to `bDoStuff@disable_lib'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status

What I would like to achieve is a way of compiling a main file, which strictly fullfills to criteria:

  1. The assignment of bDoStuff gets evaluated at compile-time;
  2. the std::cout call does not get executed; and
  3. the function call to do_stuff() gets stripped from the binary

To satisfy 3), I might need to flag with -lto, but obviously I didn't get that far yet.

Question

Is there a way to achieve 1), 2) and 3) while using C++ modules?

2

There are 2 best solutions below

2
On

What you're talking about is not a thing that could happen without modules (unless you use macros), let alone with them.

If a variable is declared constexpr, it has to have an initializer. This means it must be a definition and therefore cannot be redefined in this TU. A const qualified variable declaration also must have an initializer.

Because of this, there's no way for code outside of this TU, or later in some inclusion graph, to change what value the variable has.

Compilers can do link-time optimizations for stuff that isn't const or constexpr. But that's the best you could hope for.

Again, this is true whether you're using modules or not.

0
On

Disclaimer

I'm not completely satisfied with this solution, but for the sake of spreading knowledge I include this here as a possible solution.

If there is an answer that does not involve the use of macros, I would still be interested in hearing about it.

Macros (again)

This is a version of the library that at least works:

// disable_lib.cpp
export module disable_lib;
import <iostream>;

#if defined(DLIB_DISABLE_LOG)
constexpr bool bDoStuff = false;
#else
constexpr bool bDoStuff = true;
#endif

export void do_stuff()
{
    if constexpr (bDoStuff)
    {
        std::cout << "stuff" << std::endl;
    }
}

And the client translation unit:

import disable_lib;

int main()
{
    do_stuff();

    return 0;
}

And to compile, here with Ubuntu 23.04, g++-13, and language standard 23:

g++-13 -std=c++23 -fmodules-ts -ggdb -c disable_lib.cpp -o disable_lib.o
g++-13 -std=c++23 -fmodules-ts -ggdb disable_test.cpp disable_lib.o -o disable_test
./disable_test
stuff

g++-13 -std=c++23 -fmodules-ts -ggdb -DDLIB_DISABLE_LOG -c disable_lib.cpp -o disable_lib_release.o
g++-13 -std=c++23 -fmodules-ts -ggdb disable_test.cpp disable_lib_release.o -o disable_test_release
./disable_test_release

When compiled with the DLIB_DISABLE_LOG macro, no output is generated. Also, since the code uses if constexpr I assume that there is a compile-time check which generates an empty function.