What changes introduced in C++14 can potentially break a program written in 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:

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 )

Leave a Reply