Can C++ Code Be Valid in Both C++03 and C++11 But Do Different Things

Can C++ code be valid in both C++03 and C++11 but do different things?

The answer is a definite yes. On the plus side there is:

  • Code that previously implicitly copied objects will now implicitly move them when possible.

On the negative side, several examples are listed in the appendix C of the standard. Even though there are many more negative ones than positive, each one of them is much less likely to occur.

String literals

#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"

and

#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal

Type conversions of 0

In C++11, only literals are integer null pointer constants:

void f(void *); // #1
void f(...); // #2
template<int N> void g() {
f(0*N); // Calls #2; used to call #1
}

Rounded results after integer division and modulo

In C++03 the compiler was allowed to either round towards 0 or towards negative infinity. In C++11 it is mandatory to round towards 0

int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0

Whitespaces between nested template closing braces >> vs > >

Inside a specialization or instantiation the >> might instead be interpreted as a right-shift in C++03. This is more likely to break existing code though: (from http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observed-from-the-outside-right-angle-brackets/)

template< unsigned len > unsigned int fun(unsigned int x);
typedef unsigned int (*fun_t)(unsigned int);
template< fun_t f > unsigned int fon(unsigned int x);

void total(void) {
// fon<fun<9> >(1) >> 2 in both standards
unsigned int A = fon< fun< 9 > >(1) >>(2);
// fon<fun<4> >(2) in C++03
// Compile time error in C++11
unsigned int B = fon< fun< 9 >>(1) > >(2);
}

Operator new may now throw other exceptions than std::bad_alloc

struct foo { void *operator new(size_t x){ throw std::exception(); } }
try {
foo *f = new foo();
} catch (std::bad_alloc &) {
// c++03 code
} catch (std::exception &) {
// c++11 code
}

User-declared destructors have an implicit exception specification
example from What breaking changes are introduced in C++11?

struct A {
~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try {
A a;
} catch(...) {
// C++03 will catch the exception
}

size() of containers is now required to run in O(1)

std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03

std::ios_base::failure does not derive directly from std::exception anymore

While the direct base-class is new, std::runtime_error is not. Thus:

try {
std::cin >> variable; // exceptions enabled, and error here
} catch(std::runtime_error &) {
std::cerr << "C++11\n";
} catch(std::ios_base::failure &) {
std::cerr << "Pre-C++11\n";
}

Can code that is valid in both C and C++ produce different behavior when compiled in each language?

The following, valid in C and C++, is going to (most likely) result in different values in i in C and C++:

int i = sizeof('a');

See Size of character ('a') in C/C++ for an explanation of the difference.

Another one from this article:

#include <stdio.h>

int sz = 80;

int main(void)
{
struct sz { char c; };

int val = sizeof(sz); // sizeof(int) in C,
// sizeof(struct sz) in C++
printf("%d\n", val);
return 0;
}

What differences, if any, between C++03 and C++11 can be detected at run-time?

Core Language

Accessing an enumerator using :::

template<int> struct int_ { };

template<typename T> bool isCpp0xImpl(int_<T::X>*) { return true; }
template<typename T> bool isCpp0xImpl(...) { return false; }

enum A { X };
bool isCpp0x() {
return isCpp0xImpl<A>(0);
}

You can also abuse the new keywords

struct a { };
struct b { a a1, a2; };

struct c : a {
static b constexpr (a());
};

bool isCpp0x() {
return (sizeof c::a()) == sizeof(b);
}

Also, the fact that string literals do not anymore convert to char*

bool isCpp0xImpl(...) { return true; }
bool isCpp0xImpl(char*) { return false; }

bool isCpp0x() { return isCpp0xImpl(""); }

I don't know how likely you are to have this working on a real implementation though. One that exploits auto

struct x { x(int z = 0):z(z) { } int z; } y(1);

bool isCpp0x() {
auto x(y);
return (y.z == 1);
}

The following is based on the fact that operator int&& is a conversion function to int&& in C++0x, and a conversion to int followed by logical-and in C++03

struct Y { bool x1, x2; };

struct A {
operator int();
template<typename T> operator T();
bool operator+();
} a;

Y operator+(bool, A);

bool isCpp0x() {
return sizeof(&A::operator int&& +a) == sizeof(Y);
}

That test-case doesn't work for C++0x in GCC (looks like a bug) and doesn't work in C++03 mode for clang. A clang PR has been filed.

The modified treatment of injected class names of templates in C++11:

template<typename T>
bool g(long) { return false; }

template<template<typename> class>
bool g(int) { return true; }

template<typename T>
struct A {
static bool doIt() {
return g<A>(0);
}
};

bool isCpp0x() {
return A<void>::doIt();
}

A couple of "detect whether this is C++03 or C++0x" can be used to demonstrate breaking changes. The following is a tweaked testcase, which initially was used to demonstrate such a change, but now is used to test for C++0x or C++03.

struct X { };
struct Y { X x1, x2; };

struct A { static X B(int); };
typedef A B;

struct C : A {
using ::B::B; // (inheriting constructor in c++0x)
static Y B(...);
};

bool isCpp0x() { return (sizeof C::B(0)) == sizeof(Y); }

Standard Library

Detecting the lack of operator void* in C++0x' std::basic_ios

struct E { E(std::ostream &) { } };

template<typename T>
bool isCpp0xImpl(E, T) { return true; }
bool isCpp0xImpl(void*, int) { return false; }

bool isCpp0x() {
return isCpp0xImpl(std::cout, 0);
}

Is it safe to link C++17, C++14, and C++11 objects

Which combinations of these objects is it and isn't it safe to link into a single binary? Why?

For GCC it is safe to link together any combination of objects A, B, and C. If they are all built with the same version then they are ABI compatible, the standard version (i.e. the -std option) doesn't make any difference.

Why? Because that's an important property of our implementation which we work hard to ensure.

Where you have problems is if you link together objects compiled with different versions of GCC and you have used unstable features from a new C++ standard before GCC's support for that standard is complete. For example, if you compile an object using GCC 4.9 and -std=c++11 and another object with GCC 5 and -std=c++11 you will have problems. The C++11 support was experimental in GCC 4.x, and so there were incompatible changes between the GCC 4.9 and 5 versions of C++11 features. Similarly, if you compile one object with GCC 7 and -std=c++17 and another object with GCC 8 and -std=c++17 you will have problems, because C++17 support in GCC 7 and 8 is still experimental and evolving.

On the other hand, any combination of the following objects will work (although see note below about libstdc++.so version):

  • object D compiled with GCC 4.9 and -std=c++03
  • object E compiled with GCC 5 and -std=c++11
  • object F compiled with GCC 7 and -std=c++17

This is because C++03 support is stable in all three compiler versions used, and so the C++03 components are compatible between all the objects. C++11 support is stable since GCC 5, but object D doesn't use any C++11 features, and objects E and F both use versions where C++11 support is stable. C++17 support is not stable in any of the used compiler versions, but only object F uses C++17 features and so there is no compatibility issue with the other two objects (the only features they share come from C++03 or C++11, and the versions used make those parts OK). If you later wanted to compile a fourth object, G, using GCC 8 and -std=c++17 then you would need to recompile F with the same version (or not link to F) because the C++17 symbols in F and G are incompatible.

The only caveat for the compatibility described above between D, E and F is that your program must use the libstdc++.so shared library from GCC 7 (or later). Because object F was compiled with GCC 7, you need to use the shared library from that release, because compiling any part of the program with GCC 7 might introduce dependencies on symbols that are not present in the libstdc++.so from GCC 4.9 or GCC 5. Similarly, if you linked to object G, built with GCC 8, you would need to use the libstdc++.so from GCC 8 to ensure all symbols needed by G are found. The simple rule is to ensure the shared library the program uses at run-time is at least as new as the version used to compile any of the objects.

Another caveat when using GCC, already mentioned in the comments on your question, is that since GCC 5 there are two implementations of std::string available in libstdc++. The two implementations are not link-compatible (they have different mangled names, so can't be linked together) but can co-exist in the same binary (they have different mangled names, so don't conflict if one object uses std::string and the other uses std::__cxx11::string). If your objects use std::string then usually they should all be compiled with the same string implementation. Compile with -D_GLIBCXX_USE_CXX11_ABI=0 to select the original gcc4-compatible implementation, or -D_GLIBCXX_USE_CXX11_ABI=1 to select the new cxx11 implementation (don't be fooled by the name, it can be used in C++03 too, it's called cxx11 because it conforms to the C++11 requirements). Which implementation is the default depends on how GCC was configured, but the default can always be overridden at compile-time with the macro.

Valid C++03 template code won't compile in C++11

Clang++ gives me a warning in -std=c++03 mode:

test.cpp:6:43: warning: use of right-shift operator ('>>') in template argument
will require parentheses in C++11 [-Wc++11-compat]
value = 1 + number_of_bits<number >> 1>::value
^
( )

And indeed, in C++11 the parsing rules were revised so that >> always closes template parameters in template context. As the warning notes, you should just put parens around the parameter to fix the parsing issue:

value = 1 + number_of_bits<(number >> 1)>::value

Can internal linkage rule break a valid c++03 code in c++11?

For reference, this is the C++03 version of §14.6.4.2 [temp.dep.candidate] "Candidate functions":

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that:

  • For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the template definition context are found.
  • For the part of the lookup using associated namespaces (3.4.2), only function definitions with external linkage found in either the template definition context or the template instantiation context are found.

If the call would be ill-formed or would find a better match had the lookup within the associated namespaces considered all the function declarations with external linkage introduced in those namespaces in all translation units, not just considering those declarations found in the template definition and template instantiation contexts, then the program has undefined behaviour.

You can make some example code that would be well defined in C++03, but not C++11:

#include <iostream>

void print(short x) {
std::cout << x;
}

static void print(long x) {
std::cout << x;
}

template<typename T>
void print_twice(T x) {
print(x);
print(x);
}

int main() {
print_twice(0);
// C++03: `void print(long)` does not have external linkage so is not considered.
// Calls `void print(short)` twice

// C++11: Both `void print(long)` and `void print(short)` are viable,
// but neither is better so it is ambiguous (compile time error)
}

(Though clang and gcc don't seem to implement the C++03 version of ADL, so this would never come up when compiling with those compilers at least)

C++11 compatibility with existing libraries/frameworks

This page is dedicated to g++ abi breaks with c++11 up to version 4.7.
The first sentence there is:

The C++98 language is ABI-compatible with the C++11 language, but several places in the library break compatibility. This makes it dangerous to link C++98 objects with C++11 objects.

Though there are examples, where enabling c++11 won't brake ABI compatibility: one example is Qt where you can freely mix c++11 enabled builds with c++03 builds.

Examples of code successfully compiled in C++11 and pre-C++11 that behave differently

As chris points out, this is a duplicate of this question. However I did not see in the answers to that question the following:

#include <vector>
#include <iostream>

struct X
{
X() {std::cout << "X()\n";}
X(const X&) {std::cout << "X(const X&)\n";}
};

int
main()
{
std::vector<X> v(3);
}

In C++03 this outputs:

X()
X(const X&)
X(const X&)
X(const X&)

In C++11 this outputs:

X()
X()
X()

For almost all code, this makes no difference. However "almost" is not "always", so this is a breaking (behavioral difference) change. You can blame me personally for this change. Without it:

std::vector<std::unique_ptr<int>> v(3);

would not have compiled. And I considered this case sufficiently motivating for the breakage.



Related Topics



Leave a reply



Submit