Have there ever been silent behavior changes in C++ with new standard versions?
The return type of string::data
changes from const char*
to char*
in C++ 17. That could certainly make a difference
void func(char* data)
{
cout << data << " is not const\n";
}
void func(const char* data)
{
cout << data << " is const\n";
}
int main()
{
string s = "xyz";
func(s.data());
}
A bit contrived but this legal program would change its output going from C++14 to C++17.
The answer to this question shows how initializing a vector using a single size_type
value can result in different behavior between C++03 and C++11.
std::vector<Something> s(10);
C++03 default-constructs a temporary object of the element type Something
and copy-constructs each element in the vector from that temporary.
C++11 default-constructs each element in the vector.
In many (most?) cases these result in equivalent final state, but there is no reason they have to. It depends on the implementation of Something
's default/copy constructors.
See this contrived example:
class Something {
private:
static int counter;
public:
Something() : v(counter++) {
std::cout << "default " << v << '\n';
}
Something(Something const & other) : v(counter++) {
std::cout << "copy " << other.v << " to " << v << '\n';
}
~Something() {
std::cout << "dtor " << v << '\n';
}
private:
int v;
};
int Something::counter = 0;
C++03 will default-construct one Something
with v == 0
then copy-construct ten more from that one. At the end, the vector contains ten objects whose v
values are 1 through 10, inclusive.
C++11 will default-construct each element. No copies are made. At the end, the vector contains ten objects whose v
values are 0 through 9, inclusive.
The standard has a list of breaking changes in Annex C [diff]. Many of these changes can lead to silent behavior change.
An example:
int f(const char*); // #1
int f(bool); // #2
int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2
Every time they add new methods (and often functions) to the standard library this happens.
Suppose you have a standard library type:
struct example {
void do_stuff() const;
};
pretty simple. In some standard revision, a new method or overload or next to anything is added:
struct example {
void do_stuff() const;
void method(); // a new method
};
this can silently change the behavior of existing C++ programs.
This is because C++'s currently limited reflection capabilities are sufficient to detect if such a method exists, and run different code based on it.
template<class T, class=void>
struct detect_new_method : std::false_type {};
template<class T>
struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};
this is just a relatively simple way to detect the new method
, there are myriad of ways.
void task( std::false_type ) {
std::cout << "old code";
};
void task( std::true_type ) {
std::cout << "new code";
};
int main() {
task( detect_new_method<example>{} );
}
The same can happen when you remove methods from classes.
While this example directly detects the existence of a method, this kind of thing happening indirectly can be less contrived. As a concrete example, you might have a serialization engine that decides if something can be serialized as a container based on if it is iterable, or if it has a data pointing-to-raw-bytes and a size member, with one preferred over the other.
The standard goes and adds a .data()
method to a container, and suddenly the type changes which path it uses for serialization.
All the C++ standard can do, if it doesn't want to freeze, is to make the kind of code that silently breaks be rare or somehow unreasonable.
Oh boy... The link cpplearner provided is scary.
Among others, C++20 disallowed C-style struct declaration of C++ structs.
typedef struct
{
void member_foo(); // Ill-formed since C++20
} m_struct;
If you were taught writing structs like that (and people that teach "C with classes" teach exactly that) you're screwed.