ChatGPT解决这个技术问题 Extra ChatGPT

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

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?

I'm pretty sure auto could result in a situation like this
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.
Here's a nice article on the >> situation: gustedt.wordpress.com/2013/12/15/…
@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.
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.

1
11 revs, 8 users 80%

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

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!
But std::system_error itself is (indirectly) derived from std::exception, so catch (std::exception &) still catches std::ios_base::failure.
@user2665887 you are right. it can still influence the behaviour of a program, but i cannot think of a minimal example right now.
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.
The flip side of list::size being O(1) is that splice is now O(n)
c
chris

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.


it is weird that it does not require typename for ::two in C++03 version
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 </joke>
@zahir, It's not a type, just a value.
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, Surprisingly enough, GCC gives that warning, but Clang does not.
C
Community

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.


So most of the examples narrow down to the fact that previously undefined behaviour is now well defined?
@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.
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 yes, both unspecified and undefined behavior have undesirable results. As for extensions considering Linux depends on a number of gcc extensions we should assume in the real world they matter. I was not expecting to find so many examples when I first answered this question.
A
Angew is no longer proud of SO

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.


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.
C
Community

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::max() or std::numeric_limits::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.


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.
C
Community

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

S
StackedCrooked

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