CS253: Software Development with C++

Fall 2020

Conditional Compilation

Show Lecture.ConditionalCompilation as a slide show.

CS253 Conditional Compilation

Buridan’s Ass

Review

You can make decisions at compile-time (as opposed to run-time) via #if and friends:

cout << "alpha\n";
#if 2 > 3
    cout << "beta\n";
#elif 4 != 5
    cout << "gamma\n";
#endif
alpha
gamma

Review

You can also “comment out” sections of code:

cout << "red\n";
#if 0
    this doesn’t work yet
    cout << "white\n";
#endif
cout << "blue\n";
red
blue

Conditional Debugging

You can use a flag to control conditional compilation:

const auto now = time(nullptr);
#if DEBUG
    cout << "now is " << now << '\n';
#endif
string filename = "/tmp/temp-" + to_string(now);
cout << "filename: " << filename << '\n';
filename: /tmp/temp-1732198917

The flag DEBUG isn’t defined, so the timestamp isn’t printed.

Conditional Debugging

Let’s enable flag using #define:

#define DEBUG 1
const auto now = time(nullptr);
#if DEBUG
    cout << "now is " << now << '\n';
#endif
string filename = "/tmp/temp-" + to_string(now);
cout << "filename: " << filename << '\n';
now is 1732198917
filename: /tmp/temp-1732198917

You can also turn the flag on via: g++ -DDEBUG
which is the same as: #define DEBUG 1

Compiler Version

Most compilers provide ways to check the compiler version at compile-time. The gcc/g++ compiler provides several symbols:

cout << "Major   " << __GNUC__ << '\n'
     << "Minor   " << __GNUC_MINOR__ << '\n'
     << "Patch   " << __GNUC_PATCHLEVEL__ << '\n'
     << "Version " << __VERSION__ << '\n';
Major   8
Minor   3
Patch   1
Version 8.3.1 20191121 (Red Hat 8.3.1-5)

cout << __GNUC__ << '.'
     << __GNUC_MINOR__ << '.'
     << __GNUC_PATCHLEVEL__ << '\n';
8.3.1

Compiler Version

You can use the symbols to decide whether a feature is available:

#if __GNUC__ > 25
    // Only available on g++ starting with version 25:
    string filename = generate_temporary_filename();
#else
    // Do it the hard way:
    string filename = "/tmp/temp-" + to_string(rand())
                      + '-' + to_string(time(nullptr));
#endif
cout << "filename: " << filename << '\n';
filename: /tmp/temp-1804289383-1732198917

C++ Standard Version

Use __cplusplus to determine C++ version. Its value is the YYYYMM of standardization.

vector<long double> v;
for (int i=1; i<=10000; i++)
    v.push_back(i*i);
#if __cplusplus >= 201103L
    const auto old = v.capacity();
    v.shrink_to_fit();
    cout << old << " ➔️ " << v.capacity();
#endif
16384 ➔️ 10000
Why the L in 201103L?

A six digit number might not fit in a 16-bit int, so the standards committee made it a long literal via a trailing L.

System detection

Every compiler defines symbols that allow you to detect the compiler or architecture:

See https://sourceforge.net/p/predef/wiki/OperatingSystems/

System-Dependent Code

#if __APPLE__
    cout << "On an Apple product\n";
#elif __linux__
    cout << "On Linux\n";
#elif __hpux
    cout << "On HP-UX\n";
#else
    #error What is this, a Turing Machine!?
#endif
On Linux

Example

Partial Solution

Most compilers have system-dependent ways of solving this problem. The real problem is knowing which one to use:

Linux solution:

cout << "I am " << program_invocation_name;
I am ./a.out

HP-UX solution:

extern const char **__argv_value;
cout << "I am " << __argv_value[0];
/tmp/ccItM4AQ.o: In function `main':
c.cc:(.text+0x19): undefined reference to `__argv_value'
collect2: error: ld returned 1 exit status

Naturally, the HP-UX solution doesn’t work on Linux. ☹

Real solution

Of course, the real solution uses conditional compilation!

#if __linux__
    string pname() { return program_invocation_name; }
#elif __hpux
    extern const char **__argv_value;
    string pname() { return __argv_value[0]; }
#else
    #error Good luck with your stone knives and bearskins.
#endif

int main() {
    cout << pname() << '\n';
}
./a.out

Hide the OS-specific code in the definition of pname(). The rest of the program is clean, beautiful, and DRY.

False solution

The test must occur at compile time—can’t define a function twice!

if (__linux__) {
    string pname() { return program_invocation_name; }
}
else if (__hpux) {
    extern const char **__argv_value;
    string pname() { return __argv_value[0]; }
}
else {
    // I can’t deal with this system!
}

int main() {
    cout << pname() << '\n';
}
c.cc:1: error: expected unqualified-id before 'if'