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:
__WIN32__
__APPLE__
__linux__
__GNUC__
__hpux
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
- This code behaves differently on different systems.
- Cute, but is it useful? Yes.
Example
- Imagine that you want to write a function that returns
the current program name, for error messages.
- “No problem”, you say, “I can get that from
argv[0]
.”
- Yes, but that requires the user to pass argv to this function.
- How inconvenient!
- What if we’re nine levels deep in function calls?
- What if we’re writing a library, where we have no access to argv?
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'