When I'm building POSIX C programs, I want to be portable and use only POSIX or standard C library functions. So, for example, with gcc or clang, I build like this:
gcc -std=c99 -D_XOPEN_SOURCE=600
Setting the standard to C99 removes all extensions, then _XOPEN_SOURCE exposes POSIX interfaces. I no longer have the environment polluted with extensions from GNU, BSD, etc.
However, the waters seem murkier with C++. I want to do this:
g++ -std=c++14 -D_XOPEN_SOURCE=600
This has worked fine for me on various operating systems: Linux/glibc, Haiku, MinGW, macOS, at least. But apparently, there are problems with POSIX feature test macros and C++. Oracle docs have this to say:
C++ bindings are not defined for POSIX or SUSv4, so specifying feature test macros such as _POSIX_SOURCE, _POSIX_C_SOURCE, and _XOPEN_SOURCE can result in compilation errors due to conflicting requirements of standard C++ and these specifications.
While I don't have a copy of Oracle Solaris, I am seeing issues with FreeBSD and OpenBSD.
On FreeBSD:
#include <iostream>
int main() { }
$ clang++ -std=c++14 -D_POSIX_C_SOURCE=200112L t.cpp
In file included from t.cpp:1:
In file included from /usr/include/c++/v1/iostream:37:
In file included from /usr/include/c++/v1/ios:215:
/usr/include/c++/v1/__locale:631:16: error: use of undeclared identifier 'isascii'
return isascii(__c) ? (__tab_[static_cast<int>(__c)] & __m) !=0 : false;
...
(This builds fine with _XOPEN_SOURCE=600). C++ headers on FreeBSD use isacii, a non-standard function, so it's not exposed when _POSIX_C_SOURCE is set.
Or on OpenBSD:
#include <fstream>
int main() { }
$ clang++ -std=c++14 -D_XOPEN_SOURCE=600 t.cpp
In file included from t.cpp:1:
In file included from /usr/include/c++/v1/fstream:183:
In file included from /usr/include/c++/v1/ostream:138:
In file included from /usr/include/c++/v1/ios:215:
In file included from /usr/include/c++/v1/__locale:32:
In file included from /usr/include/c++/v1/support/newlib/xlocale.h:25:
/usr/include/c++/v1/support/xlocale/__strtonum_fallback.h:23:64: error: unknown type name 'locale_t'
char **endptr, locale_t) {
Presumably <locale.h> isn't getting included somewhere it “should” be.
The worrisome conclusion I'm drawing is that you can't portably have a POSIX C++ environment that is free of non-POSIX extensions. These examples work fine on OpenBSD and FreeBSD if the feature test macros are removed. That looks to be because the BSD headers expose BSD functions unless in standard C mode, but they do not care about standard C++ mode (they explicitly check whether macros corresponding to C89, C99, or C11 are set). glibc looks to be the same: it still exposes non-standard C functions in standard C++ mode, so perhaps it's only a matter of time before I run into a build error there.
So the actual question is this: can you write portable POSIX C++ code which does not expose platform-specific functionality? Or if I'm targeting POSIX with C++ should I just not set any feature test macros and hope for the best?
EDIT:
I got to thinking about the implications of this (as in, why do I care?), and the following program, I think, illustrates it. This is Linux/glibc:
#include <ctime>
int dysize;
$ g++ -c -std=c++14 t.cpp
t.cpp:2:5: error: ‘int dysize’ redeclared as different kind of entity
2 | int dysize;
| ^~~~~~
In file included from t.cpp:1:
/usr/include/time.h:262:12: note: previous declaration ‘int dysize(int)’
262 | extern int dysize (int __year) __THROW __attribute__ ((__const__));
This is the standard <ctime> header, which is does not include anything called dysize. That's an old SunOS function that glibc includes for compatibility. A C program built with -std=c99 won't expose it, but C++ always does. And there's no real way of knowing which non-reserved identifiers will be used by various implementations. If -std=c++14 caused non-standard identifiers to be hidden, that would avoid this problem, but it doesn't, and I can't see a way around that.
Which might imply that the feature test macro is a red herring: the source of the problem is that, on at least some real-world implementations, C++ compilers are exposing symbols they're not supposed to.