301

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?

Angew is no longer proud of SO
  • 156,801
  • 13
  • 318
  • 412
Erik Sjölund
  • 9,109
  • 7
  • 34
  • 60
  • 26
    I'm pretty sure `auto` could result in a situation like this – OMGtechy Apr 13 '14 at 19:15
  • 8
    Yes. One example is `>>` when used in a template. You can come up with a situation where it can compile for both standards. Another one that I'm sure would be easy to find changes for is in initialization. – chris Apr 13 '14 at 19:15
  • 1
    Have you already looked into the Annex C about compatibility, more specifically [diff.cpp03]? – dyp Apr 13 '14 at 19:18
  • 5
    Here's a nice article on the >> situation: http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observed-from-the-outside-right-angle-brackets/ – chris Apr 13 '14 at 19:34
  • 6
    @OMGtechy: I don't *think* `auto` can cause this. With the old meaning, an `auto` declaration requires a type name; with the new meaning, a type name is not permitted. – Keith Thompson Apr 13 '14 at 19:38
  • 1
    @Ali it is not a duplicate. The two questions ask entirely different thing. Just because there is some overlap to the answers does not make them duplicates. – jalf Apr 13 '14 at 20:01
  • @chris Would you mind fleshing the `>>` issue out into an answer so that we can vote on it? – cmaster - reinstate monica Apr 13 '14 at 20:04
  • @cmaster, Sure, I guess. As for initialization, there has to be something, but all I can think of offhand would be different behaviour between now and C++17 (hopefully). – chris Apr 13 '14 at 20:12
  • From the link by Ali these would do different things: first two define things. long long literals. interger divition. throwing destructor. >> operator. dependent calls of funcs with internal linkage. – typ1232 Apr 13 '14 at 20:18
  • 1
    @Ali I still can't agree with closing this question. The nature of this question makes it much more clearer where there are *real* differences between the standards, and not just some obsolent features. And, do you really want to close a question with seven upvotes and two stars? – cmaster - reinstate monica Apr 13 '14 at 20:34
  • 2
    How is it open-ended? You yourself pointed out through another question that the answer to this question is "yes, here is an example of how". There is a very definite answer to the question, as you yourself pointed out. – jalf Apr 13 '14 at 21:02
  • While not the 'the same code being valid in both' you easily *compile* for both by defining a macro such as `#define CPP_03 1` and later `#ifdef CPP_03` (c++03 code here..) `#elif defined CPP_11` (c++11 code...) `#endif`. You could pick which to define based on your compiler's predefined `__cplusplus` value if you need it to be dynamic. – Garet Claborn Apr 14 '14 at 13:48
  • 2
    Closing this as too broad would be a shame, this is question likely to be asked many times and it would be good to have a solid canonical question to point to that is not closed. – Shafik Yaghmour Apr 15 '14 at 13:15
  • I have ten different sections and about 15 different examples with different results and amazingly and there is more if we include SFINAE cases. Wow! not what I expected when I originally answered. Not sure when I will add more since I have to figure out how to manage the size of the answer. – Shafik Yaghmour Apr 16 '14 at 03:25
  • @dyp Not yet, but I will. – Erik Sjölund Apr 17 '14 at 13:30
  • When I wrote the question I was mostly interested in getting a yes-or-no-answer. A yes answer could of course be backed up with an example. Now a lot of examples have been given. But I don't have a good idea of how to narrow the answer set. Maybe I'll come up with something ... – Erik Sjölund Apr 17 '14 at 13:40
  • A background to my interest in the question: A "yes" answer would imply that the possibilities to auto-detect the C++ standard from the C++ code is limitied – Erik Sjölund Apr 17 '14 at 13:54
  • 1
    Considering that the dup currently listed actually is a subset of the question asked here, should that one be closed as a dup of this one? – Shafik Yaghmour Jun 03 '14 at 02:47
  • @KeithThompson `auto` in C++03 doesn't need a type name if you mean implicit `int`. See also http://nullprogram.com/blog/2016/10/02/ – Tanz87 Nov 08 '16 at 20:36
  • 1
    @Tanz87: I don't think that helps in this case. `auto n = 42;` means the same thing in C90 and C++11, but it's invalid in C++03 (which didn't have the new meaning of `auto`, and didn't have the implicit `int` rule) and in C99 (which dropped the implicit `int` rule from C90). – Keith Thompson Nov 08 '16 at 20:40

7 Answers7

286

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";
}
Ted Lyngmo
  • 37,764
  • 5
  • 23
  • 50
example
  • 3,119
  • 1
  • 18
  • 27
  • 11
    Nice, +1. Another one is that a user declared destructor now is implicitly `noexecpt(true)` so `throw` in a destructor will now call `std::terminate`. But I hope anyone who wrote such code will be happy about this! – typ1232 Apr 13 '14 at 20:55
  • 4
    But std::system_error itself is (indirectly) derived from std::exception, so `catch (std::exception &)` still catches `std::ios_base::failure`. – user2665887 Apr 13 '14 at 21:25
  • @user2665887 you are right. it can still influence the behaviour of a program, but i cannot think of a minimal example right now. – example Apr 13 '14 at 21:29
  • 5
    I'm super confused, as what you say about `operator new` is accurate (it can now throw `std::bad_array_new_length`), but your example doesn't show that at all. The code you show is the same in C++03 and C++11 AFAIK. – Mooing Duck Apr 14 '14 at 23:43
  • 2
    The flip side of list::size being O(1) is that splice is now O(n) – Tony Delroy Apr 17 '14 at 00:56
  • Question: did any compiler actually round division to negative infinity before C++11? – user541686 Jul 19 '15 at 01:12
  • Could someone please explain the string literals change? I can't make sense of the new behaviour. – Max Barraclough May 06 '18 at 12:47
  • @MaxBarraclough Now `0 * N` will never bind to `void *`. – bipll Aug 15 '18 at 14:34
56

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.

chris
  • 55,166
  • 13
  • 130
  • 185
  • it is weird that it does not require `typename` for `::two` in C++03 version – zahir Apr 13 '14 at 20:34
  • 3
    Nice one, getting it to boil down to evaluate to `true` or `false` for the different standards. Maybe we could use it as a feature test – cmaster - reinstate monica Apr 13 '14 at 20:38
  • @zahir, It's not a type, just a value. – chris Apr 13 '14 at 20:39
  • well, proper cmdline options warn about this (`warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]`), but still a nice example of how the ambiguous `::` operator changes meaning (either refering to the global scope or dereferencing the one standing directly before it) – example Apr 13 '14 at 21:04
  • @example, Surprisingly enough, GCC gives that warning, but Clang does not. – chris Apr 13 '14 at 21:30
  • @chris strange indeed. usually clang shows many more warnings, and indeed i would assume that code that is written like this is faulty and thus justifies a warning – example Apr 13 '14 at 21:32
  • @example if you see number 7 in my answer `clang` allows templates arguments with internal linkage in C++03 with only a warning, which I found rather surprising as well. The answers show a lot of surprises are to be found. – Shafik Yaghmour Apr 15 '14 at 16:24
  • @ShafikYaghmour, Indeed, Clang is usually the better one :/ – chris Apr 15 '14 at 19:45
39

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.

Community
  • 1
  • 1
Shafik Yaghmour
  • 143,425
  • 33
  • 399
  • 682
  • So most of the examples narrow down to the fact that previously undefined behaviour is now well defined? – MatthiasB Apr 15 '14 at 09:14
  • @MatthiasB 2, 3 and 4 are about this so at this point they are not a majority of the examples anymore. I doubt I will find many more undefined behavior examples so as I add more then they will become a smaller set. – Shafik Yaghmour Apr 15 '14 at 09:25
  • Well, #1 behaviour is unspecified, so i'd count it as undefined behaviour (at least you cannot expect to get a specific result with c++03, now with c++11 you can), #5 uses a non-standard extension of c++. But I guess you're right. The more you look for it, the more examples you'll find which are defined in both standards but produce different results. – MatthiasB Apr 15 '14 at 09:53
  • @MatthiasB yes, both unspecified and undefined behavior have undesirable results. As for extensions considering Linux [depends on a number of gcc extensions](http://www.ibm.com/developerworks/library/l-gcc-hacks/) we should assume in the real world they matter. I was not expecting to find so many examples when I first answered this question. – Shafik Yaghmour Apr 15 '14 at 17:36
35

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.

Angew is no longer proud of SO
  • 156,801
  • 13
  • 318
  • 412
  • 3
    While this is obviously a breaking change, I prefer the C++11 behaviour. I'd expect this to result in a `deque` holding ten separate widgets, not ten widgets sharing the same resource. – Agentlien Apr 14 '14 at 06:58
20

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.

Community
  • 1
  • 1
Anton Golov
  • 3,224
  • 1
  • 18
  • 39
  • 4
    You might add, that in C++03 one could have used this *standardized behavior* to provide a default value, as in `int x = 1, y = 1; cin >> x >> y; cout << x*y;`. With C++03, this would have correctly produce `x` when no `y` could be read. – cmaster - reinstate monica Apr 14 '14 at 18:35
16

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);
}
Community
  • 1
  • 1
uwedolinsky
  • 611
  • 3
  • 8
7

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

StackedCrooked
  • 32,392
  • 40
  • 137
  • 267