CS253: Software Development with C++

Spring 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-1714074017

The flag DEBUG isn’t defined, so the process id 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 1714074017
filename: /tmp/temp-1714074017

You can also turn the flag on via: g++ -DDEBUG

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';
Major 8
Minor 3
Patch 1

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
    string filename = generate_temporary_filename();
#else
    string filename = "/tmp/temp-" + to_string(rand())
                      + '-' + to_string(time(nullptr));
#endif
cout << "filename: " << filename << '\n';
filename: /tmp/temp-1804289383-1714074017

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

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 __linux__
    cout << "On Linux\n";
#endif
#if __APPLE__
    cout << "On an Apple product\n";
#endif
#if __hpux
    cout << "On HP-UX\n";
#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/ccDcamGL.o: In function `main':
c.cc:(.text+0x19): undefined reference to `__argv_value'
collect2: error: ld returned 1 exit status

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 I can’t deal with this system!
#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'