What Breaking Changes Are Introduced in C++11

What changes introduced in C++14 can potentially break a program written in C++11?

Note: In this post I consider a "breaking change" to be either, or both, of;

1. a change that will make legal C++11 ill-formed when compiled as C++14, and;

2. a change that will change the runtime behavior when compiled as C++14, vs C++11.


C++11 vs C++14, what does the Standard say?

The Standard draft (n3797) has a section dedicated for just this kind of information, where it describes the (potentially breaking) differences between one revision of the standard, and another.

This post has used that section, [diff.cpp11], as a base for a semi-elaborate discussion regarding the changes that could affect code written for C++11, but compiled as C++14.


C.3.1] Digit Separators

The digit separator was introduced so that one could, in a more readable manner, write numeric literals and split them up in a way that is more natural way.

int x = 10000000;   // (1)
int y = 10'000'000; // (2), C++14

It's easy to see that (2) is much easier to read than (1) in the above snippet, while both initializers have the same value.

The potential issue regarding this feature is that the single-quote always denoted the start/end of a character-literal in C++11, but in C++14 a single-quote can either be surrounding a character-literal, or used in the previously shown manner (2).


Example Snippet, legal in both C++11 and C++14, but with different behavior.

#define M(x, ...) __VA_ARGS__

int a[] = { M(1'2, 3'4, 5) };

// int a[] = { 5 }; <-- C++11
// int a[] = { 3'4, 5 }; <-- C++14
// ^-- semantically equivalent to `{ 34, 5 }`

( Note: More information regarding single-quotes as digit separators can be found in n3781.pdf )


C.3.2] Sized Deallocation

C++14 introduces the opportunity to declare a global overload of operator delete suitable for sized deallocation, something which wasn't possible in C++11.

However, the Standard also mandates that a developer cannot declare just one of the two related functions below, it must declare either none, or both; which is stated in [new.delete.single]p11.

void operator delete (void*) noexcept;
void operator delete (void*, std::size_t) noexcept; // sized deallocation


Further information regarding the potential problem:

Existing programs that redefine the global unsized version do not also
define the sized version. When an implementation introduces a sized
version, the replacement would be incomplete and it is likely that
programs would call the implementation-provided sized deallocator on
objects allocated with the programmer-provided allocator.


Note: Quote taken from n3536 - C++ Sized Deallocation

( Note: More of interest is available in the paper titled n3536 - C++ Sized Deallocation, written by Lawrence Crowl )


C.3.3] constexpr member-functions, no longer implicitly const

There are many changes to constexpr in C++14, but the only change that will change semantics between C++11, and C++14 is the constantness of a member-function marked as constexpr.

The rationale behind this change is to allow constexpr member-functions to mutate the object to which they belong, something which is allowed due to the relaxation of constexpr.

struct A { constexpr int func (); };

// struct A { constexpr int func () const; }; <-- C++11
// struct A { constexpr int func (); }; <-- C++14


Recommended material on this change, and why it is important enough to introduce potential code-breakage:

  • Andrzej's C++ blog - “constexpr” function is not “const”
  • open-std.org - constexpr member functions and implicit const
  • (open-std.org - Relaxing constraints on constexpr functions)


Example snippet, legal in both C++11 and C++14, but with different behavior

struct Obj {
constexpr int func (int) {
return 1;
}

constexpr int func (float) const {
return 2;
}
};

Obj const a = {}; 
int const x = a.func (123);

// int const x = 1; <-- C++11
// int const x = 2; <-- C++14

C.3.4] Removal of std::gets

std::gets has been removed from the Standard Library because it is considered dangerous.

The implications of this is of course that trying to compile code written for C++11, in C++14, where such a function is used will most likely just fail to compile.


( Note: there are ways of writing code that doesn't fail to compile, and have different behavior, that depends on the removal of std::gets from the Standard Library )

std::vector, default construction, C++11 and breaking changes

Does the C++03 standard mandate that std::vector must have a constructor defined as above, i.e. with a default argument? In particular is there a guarantee that the entries of the vector object get copied instead of default constructed?

Yes, the specified behavior is that x is copied n times so that the container is initialized to contain with n elements that are all copies of x.


What does the C++11 Standard say about this same point?

In C++11 this constructor has been turned into two constructors.

vector(size_type n, const T& x, const Allocator& = Allocator()); // (1)
explicit vector(size_type n); // (2)

Except for the fact that it no longer has a default argument for the second parameter, (1) works the same way as it does in C++03: x is copied n times.

In lieu of the default argument for x, (2) has been added. This constructor value-initializes n elements in the container. No copies are made.

If you require the old behavior, you can ensure that (1) is called by providing a second argument to the constructor invocation:

std::vector<S> v(42, S());

I see this as a possibility for a breaking change between C++03 and C++11. I see this as a possibility for a breaking change between C++03 and C++11. Has this issue been investigated? Solved?

Yes, as your example demonstrates, this is indeed a breaking change.

As I am not a member of the C++ standardization committee (and I haven't paid particularly close attention to library-related papers in the mailings), I don't know to what degree this breaking change was discussed.

What are the new reserved words in C++11?

alignas
alignof
char16_t
char32_t
constexpr
decltype
default
export
noexcept
nullptr
static_assert
thread_local
using

override and final are reserved only in certain contexts.

http://en.cppreference.com/w/cpp/keyword

Is there a specific time to use c++11 over c++17

I feel like this is going to get closed as vague, opinion based, but what the heck. Keep in mind that 14 is between 11 and 17 as well, and much more widely supported. There's basically no reason to use 11 over 14 for a new codebase now, that I can think of. For 17, the problem is like you said, compiler support. If you need to target windows, MSVC is still missing tons of 17 support: http://en.cppreference.com/w/cpp/compiler_support#C.2B.2B17_features.

In order to write cross platform, 17 code right now, you almost certainly need some heavy duty continuous integration that will build and run your code on at least 2 if not 3 compilers every time you push.

If you are not targeting windows, or even if you can just commit to a single compiler, I don't see any reason not to use 17. (by that I mean: if you are only targeting MSVC, it's 17 support is partway there but there's little downside to using whats available, if something isn't supported it won't build. It's when you support different compilers that support different things, and you aren't building them all locally that it becomes a nightmare).

What changes between C++98 and C++11 show difference in behavior?

Big one that stands out -- throwing exceptions from destructors.

In C++98 you can have programs that do this and work fine if you are careful.

In C++11 you will often have to explicitly declare the dtor noexcept(false).

Nice blog post here, on Andrzej's C++ blog.

In short, the following program used to run successfully in C++03 (under some definition of “success”):

struct S
{
~S() { throw runtime_error(""); } // bad, but acceptable
};

int main()
{
try { S s; }
catch (...) {
cerr << "exception occurred";
}
cout << "success";
}

In C++11, the same program will trigger the call to std::terminate.

What is 2D vector construction breaking change in C++11?

I emailed Stephan and asked what he was talking about. Here's his answer (edited for formatting). It didn't sound like he was planning to post the answer here; if he does, I'll delete this copy.

Everything from here down is Stephan speaking.

I was referring to this:

#include <vector>
using namespace std;

int main() {
vector<vector<int>> v(11, 22);
}

It compiles with VC10 SP1 (following C++03), but not with VC11 RTM (following C++11): [snip error message dump]

C++03 23.1.1 [lib.sequence.reqmts]/9 said:

For every sequence defined in this clause and in clause 21:

— the constructor

template <class InputIterator> X(InputIterator f, InputIterator l, const Allocator& a = Allocator())
shall have the same effect
as:

X(static_cast<typename X::size_type>(f), static_cast<typename X::value_type>(l), a)

if InputIterator is an integral type.

This transformed vector<vector<int>> v(11, 22) to vector<vector<int>> v(static_cast<size_t>(11), static_cast<vector<int>>(22)), which is valid. (static_cast is capable of invoking explicit constructors, like vector's size constructor.)

C++11 23.2.3 [sequence.reqmts]/14 says:

For every sequence container defined in this Clause and in Clause 21:

— If the constructor

template <class InputIterator> X(InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type())

is called with a type InputIterator that does not qualify as an input iterator, then the constructor shall not participate in overload resolution.

This removes the (InIt, InIt) ctor from overload resolution. That leaves (size_type n, const T& value), where T is vector<int>. However, that would try to implicitly convert 22 to a temporary vector<int> (in order to bind it to const T&). The explicitness of vector's size constructor forbids that.

Reading the other SO question, this is a different issue.

STL

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";
}

Any disadvantages of migration to c++11 if my project now full on c++98

Explicit operator bool is a major breakdown regarding compatibility.

However if you want to know all the nuances and changes for the new version I recommend you checking the ISO C++ committee approved Final Draft International Standard (FDIS) for the C++ programming language. It has a section for incompatibilities at appendix C.2 "C++ and ISO C++ 2003"

Some of them are summarized in this answer on SO.

I can summarize some common pitfalls for you:

  • Watch out for new keywords, string literals and other core differences in the syntax of the language
    e.g.

    #define u8<-- "abc"
  • Destructors are now implicitly "no-throw" (and frankly throwing exceptions from destructor is a terrible practice)

  • Functions with internal linkage are considered when searching for dependencies as well



Related Topics



Leave a reply



Submit