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

C++C++11Language LawyerC++03

C++ Problem Overview


Is it possible for C++ code to conform to both the C++03 standard and the C++11 standard, but do different things depending on under which standard it is being compiled?

C++ Solutions


Solution 1 - C++

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 https://stackoverflow.com/questions/6399615/what-breaking-changes-are-introduced-in-c11

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

Solution 2 - C++

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.

Solution 3 - C++

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.

Solution 4 - C++

One potentially dangerous backward-incompatible change is in constructors of sequence containers such as std::vector, specifically in the overload specifying initial size. Where in C++03, they copied a default-constructed element, in C++11 they default-construct each one.

Consider this example (using boost::shared_ptr so that it's valid C++03):

#include <deque>
#include <iostream>

#include "boost/shared_ptr.hpp"


struct Widget
{
  boost::shared_ptr<int> p;

  Widget() : p(new int(42)) {}
};


int main()
{
  std::deque<Widget> d(10);
  for (size_t i = 0; i < d.size(); ++i)
    std::cout << "d[" << i << "] : " << d[i].p.use_count() << '\n';
}

C++03 Live example

C++11 Live example

The reason is that C++03 specified one overload for both "specify size and prototype element" and "specify size only," like this (allocator arguments omitted for brevity):

container(size_type size, const value_type &prototype = value_type());

This will always copy prototype into the container size times. When called with just one argument, it will therefore create size copies of a default-constructed element.

In C++11, this constructor signature was removed and replaced with these two overloads:

container(size_type size);

container(size_type size, const value_type &prototype);

The second one works as before, creating size copies of the prototype element. However, the first one (which now handles calls with only the size argument specified) default-constructs each element individually.

My guess for the reason of this change is that the C++03 overload wouldn't be usable with a move-only element type. But it's a breaking change none the less, and one seldom documented at that.

Solution 5 - C++

The result of a failed read from an std::istream has changed. CppReference summarizes it nicely:

> If extraction fails (e.g. if a letter was entered where a digit is expected), value is left unmodified and failbit is set. (until C++11) > > If extraction fails, zero is written to value and failbit is set. If extraction results in the value too large or too small to fit in value, std::numeric_limits<T>::max() or std::numeric_limits<T>::min() is written and failbit flag is set. (since C++11)

This is primarily an issue if you are used to the new semantics and then have to write using C++03. The following is not particularly good practice but well-defined in C++11:

int x, y;
std::cin >> x >> y;
std::cout << x + y;

However, in C++03, the above code uses an uninitialized variable and thus has undefined behaviour.

Solution 6 - C++

This thread What differences, if any, between C++03 and C++0x can be detected at run-time has examples (copied from that thread) to determine language differences, for example by exploiting C++11 reference collapsing:

template <class T> bool f(T&) {return true; } 
template <class T> bool f(...){return false;} 

bool isCpp11() 
{
    int v = 1;
    return f<int&>(v); 
}

and c++11 allowing local types as template parameters:

template <class T> bool cpp11(T)  {return true;} //T cannot be a local type in C++03
                   bool cpp11(...){return false;}

bool isCpp0x() 
{
   struct local {} var; //variable with local type
   return cpp11(var);
}

Solution 7 - C++

Here's another example:

#include <iostream>

template<class T>
struct has {
  typedef char yes;
  typedef yes (&no)[2];    
  template<int> struct foo;    
  template<class U> static yes test(foo<U::bar>*);      
  template<class U> static no  test(...);    
  static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};

enum foo { bar };

int main()
{
    std::cout << (has<foo>::value ? "yes" : "no") << std::endl;
}

Prints:

Using c++03: no
Using c++11: yes

See the result on Coliru

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionErik Sj&#246;lundView Question on Stackoverflow
Solution 1 - C++exampleView Answer on Stackoverflow
Solution 2 - C++chrisView Answer on Stackoverflow
Solution 3 - C++Shafik YaghmourView Answer on Stackoverflow
Solution 4 - C++Angew is no longer proud of SOView Answer on Stackoverflow
Solution 5 - C++Anton GolovView Answer on Stackoverflow
Solution 6 - C++uwedolinskyView Answer on Stackoverflow
Solution 7 - C++StackedCrookedView Answer on Stackoverflow