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";
}
I point you to this article and the follow-up, which has a nice example of how >>
can change meaning from C++03 to C++11 while still compiling in both.
bool const one = true;
int const two = 2;
int const three = 3;
template<int> struct fun {
typedef int two;
};
template<class T> struct fon {
static int const three = ::three;
static bool const one = ::one;
};
int main(void) {
fon< fun< 1 >>::three >::two >::one; // valid for both
}
The key part is the line in main
, which is an expression.
In C++03:
1 >> ::three = 0
=> fon< fun< 0 >::two >::one;
fun< 0 >::two = int
=> fon< int >::one
fon< int >::one = true
=> true
In C++11
fun< 1 > is a type argument to fon
fon< fun<1> >::three = 3
=> 3 > ::two > ::one
::two is 2 and ::one is 1
=> 3 > 2 > 1
=> (3 > 2) > 1
=> true > 1
=> 1 > 1
=> false
Congratulations, two different results for the same expression. Granted, the C++03 one did come up with a warning form Clang when I tested it.
Yes, there are number of changes that will cause the same code to result in different behavior between C++03 and C++11. The sequencing rules differences make for some interesting changes including some previously undefined behavior becoming well defined.
1. multiple mutations of the same variable within an initializer list
One very interesting corner case would multiple mutations of the same variable within an initializer list, for example:
int main()
{
int count = 0 ;
int arrInt[2] = { count++, count++ } ;
return 0 ;
}
In both C++03 and C++11 this is well defined but the order of evaluation in C++03 is unspecified but in C++11 they are evaluated in the order in which they appear. So if we compile using clang
in C++03 mode it provide the following warning (see it live):
warning: multiple unsequenced modifications to 'count' [-Wunsequenced]
int arrInt[2] = { count++, count++ } ;
^ ~~
but does not provide a warning in C++11 (see it live).
2. New sequencing rules make i = ++ i + 1; well defined in C++11
The new sequencing rules adopted after C++03 means that:
int i = 0 ;
i = ++ i + 1;
is no longer undefined behavior in C++11, this is covered in defect report 637. Sequencing rules and example disagree
3. New sequencing rules also make ++++i ; well defined in C++11
The new sequencing rules adopted after C++03 means that:
int i = 0 ;
++++i ;
is no longer undefined behavior in C++11.
4. Slightly More Sensible Signed Left-Shifts
Later drafts of C++11 include N3485
which I link below fixed the undefined behavior of shifting a 1 bit into or past the sign bit. This is also covered in defect report 1457. Howard Hinnant commented on the significance of this change in the thread on Is left-shifting (<<) a negative integer undefined behavior in C++11?.
5. constexpr functions can be treated as compile time constant expressions in C++11
C++11 introduced constexpr functions which:
The constexpr specifier declares that it is possible to evaluate the value of the function or variable at compile time. Such variables and functions can then be used where only compile time constant expressions are allowed.
while C++03 does not have the constexpr feature we don't have to explicitly use the constexpr keyword since the standard library provides many functions in C++11 as constexpr. For example std::numeric_limits::min. Which can lead to different behavior, for example:
#include <limits>
int main()
{
int x[std::numeric_limits<unsigned int>::min()+2] ;
}
Using clang
in C++03 this will cause x
to be a variable length array, which is an extension and will generate the following warning:
warning: variable length arrays are a C99 feature [-Wvla-extension]
int x[std::numeric_limits<unsigned int>::min()+2] ;
^
while in C++11 std::numeric_limits<unsigned int>::min()+2
is a compile time constant expression and does not require the VLA extension.
6. In C++11 noexcept exception specifications are implicitly generated for your destructors
Since in C++11 user defined destructor has implicit noexcept(true)
specification as explained in noexcept destructors it means that the following program:
#include <iostream>
#include <stdexcept>
struct S
{
~S() { throw std::runtime_error(""); } // bad, but acceptable
};
int main()
{
try { S s; }
catch (...) {
std::cerr << "exception occurred";
}
std::cout << "success";
}
In C++11 will call std::terminate
but will run successfully in C++03.
7. In C++03, template arguments could not have internal linkage
This is covered nicely in Why std::sort doesn't accept Compare classes declared within a function. So the following code should not work in C++03:
#include <iostream>
#include <vector>
#include <algorithm>
class Comparators
{
public:
bool operator()(int first, int second)
{
return first < second;
}
};
int main()
{
class ComparatorsInner : public Comparators{};
std::vector<int> compares ;
compares.push_back(20) ;
compares.push_back(10) ;
compares.push_back(30) ;
ComparatorsInner comparatorInner;
std::sort(compares.begin(), compares.end(), comparatorInner);
std::vector<int>::iterator it;
for(it = compares.begin(); it != compares.end(); ++it)
{
std::cout << (*it) << std::endl;
}
}
but currently clang
allows this code in C++03 mode with a warning unless you use -pedantic-errors
flag, which is kind of icky, see it live.
8. >> is not longer ill-formed when closing multiple templates
Using >>
to close multiple templates is no longer ill-formed but can lead to code with different results in C++03 and C+11. The example below is taken from Right angle brackets and backwards compatibility:
#include <iostream>
template<int I> struct X {
static int const c = 2;
};
template<> struct X<0> {
typedef int c;
};
template<typename T> struct Y {
static int const c = 3;
};
static int const c = 4;
int main() {
std::cout << (Y<X<1> >::c >::c>::c) << '\n';
std::cout << (Y<X< 1>>::c >::c>::c) << '\n';
}
and the result in C++03 is:
0
3
and in C++11:
0
0
9. C++11 changes some of std::vector constructors
Slightly modified code from this answer shows that using the following constructor from std::vector:
std::vector<T> test(1);
produces different results in C++03 and C++11:
#include <iostream>
#include <vector>
struct T
{
bool flag;
T() : flag(false) {}
T(const T&) : flag(true) {}
};
int main()
{
std::vector<T> test(1);
bool is_cpp11 = !test[0].flag;
std::cout << is_cpp11 << std::endl ;
}
10. Narrowing conversions in aggregate initializers
In C++11 a narrowing conversion in aggregate initializers is ill-formed and it looks like gcc
allows this in both C++11 and C++03 although it provide a warning by default in C++11:
int x[] = { 2.0 };
This is covered in the draft C++11 standard section 8.5.4
List-initialization paragraph 3:
List-initialization of an object or reference of type T is defined as follows:
and contains the following bullet (emphasis mine):
Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed
This and many more instance are covered in the draft C++ standard section annex C.2
C++ and ISO C++ 2003. It also includes:
New kinds of string literals [...] Specifically, macros named R, u8, u8R, u, uR, U, UR, or LR will not be expanded when adjacent to a string literal but will be interpreted as part of the string literal. For example
#define u8 "abc" const char *s = u8"def"; // Previously "abcdef", now "def"
User-defined literal string support [...]Previously, #1 would have consisted of two separate preprocessing tokens and the macro _x would have been expanded. In this International Standard, #1 consists of a single preprocessing tokens, so the macro is not expanded.
#define _x "there" "hello"_x // #1
Specify rounding for results of integer / and % [...] 2003 code that uses integer division rounds the result toward 0 or toward negative infinity, whereas this International Standard always rounds the result toward 0.
Complexity of size() member functions now constant [...] Some container implementations that conform to C++ 2003 may not conform to the specified size() requirements in this International Standard. Adjusting containers such as std::list to the stricter requirements may require incompatible changes.
Change base class of std::ios_base::failure [...] std::ios_base::failure is no longer derived directly from std::exception, but is now derived from std::system_error, which in turn is derived from std::runtime_error. Valid C++ 2003 code that assumes that std::ios_base::failure is derived directly from std::exception may execute differently in this International Standard.