C++11 added multiple smaller language features as well as the marquee features we’ve already talked about. Despite these being smaller language features, they’re still powerful utilities that have changed the way in which we write modern C++ programs.
Range-based for loops
std::vector<int> v;
// Add values to v.
for(int i : v) {
// Process each element of v; i holds the current element.
}
Under the hood, this construct is syntactic sugar for a regular for loop and is effectively rewritten to:
std::vector<int> v;
// Add values to v.
{
auto &&__range = v;
auto __begin = begin(__range);
auto __end = end(__range);
for (; __begin != __end; ++__begin) {
int i = *__begin;
// Process each element of v; i holds the current element.
}
}
Range-based for loops will loop over arrays, or any data type which exposes a .begin()/.end()
pair of member functions, or for which you can use ADL (not ordinary unqualified lookups) to call begin(__range)
and end(__range)
on.
Attributes
A personal favorite topic of mine! C++11 added syntax allowing users to specify standards-mandated or vendor-supplied attributes. This solves an incredibly annoying, long-standing problem where different vendors have invented different syntaxes to allow the user to specify extra information about source constructs to the compiler. This may seem like a minor annoyance, but try writing a cross-platform C++ program that makes use of vendor attributes and you’ll quickly find yourself mired in a labyrinth of macros, 1- or 0-based function parameter position values, and other difficulties. With C++ attributes, the attribute always appertains to the entity immediately to the *left* of the attribute, or, if the attribute is at the start of a list of declarations, it applies to all declarations in the list.
[[clang::foobar]] int a, b, c; // foobar appertains to a, b, and c.
int a, b [[clang::foobar]], c; // foobar appertains to b, but not a or c.
Deleted and defaulted functions
When writing a class definition, sometimes you want to make a special member function, like a copy constructor and copy assignment operator, unavailable. It’s not that you don’t want to declare the member function at all (because one may be automatically generated for you by the compiler), it’s that you want an attempted use of that function to result in an error. In the old days, you would make those functions private and not provide a definition:
struct S {
private:
S(const S&); // Do not define
S& operator=(const S&); // Do not define
};
If the function is selected by overload resolution, then it’s either private to the class (and the user gets an error about attmepting to access a private function), or it’s not been defined (and the user gets a link error about using a function with no definition). However, C++11 provides a more elegant solution in that it allows you to explicitly “delete” the function — this means the function name exists, but if the function is called, the caller (no matter the context) gets an error about attempting to use a deleted function.
struct S {
S(const S&) = delete;
S& operator=(const S&) = delete;
};
Similarly, there are times when a special member function definition is *required* but the user may not care about its definition because they know the default behavior the compiler would provide is sufficient. For instance, imagine a base class that defines some virtual functions; it also needs to have a virtual destructor or else you will get bad polymorphic delete behavior, as in:
struct S {
virtual ~S() {} // Do nothing
virtual void foo();
};
Similar to how you can explicitly delete a function, you can explicitly default the implementation of a special member function as well. This provides a function definition that is identical to what the implicitly defaulted definition would do.
struct S {
virtual ~S() = default;
virtual void foo();
};
One thing to note is that you can only explicitly default special member functions, but you can explicitly delete any function you want. This is sometimes helpful in circumstances where you want to remove a candidate from an overload set so that attempting to call it will cause a hard error. Consider:
template <typename IntegralType>
void foo(IntegralType i) = delete;
template <>
void foo<int>(int i) { /* Do something useful. */ }
template <>
void foo<unsigned int>(unsigned int) { /* Do something useful. */ }
int main() {
foo(12); // Okay
foo(1.2); // Error: call to deleted function 'foo'
}
Other Interesting New C++ Features
I don’t have time to cover all the other language features that also contribute to writing modern C++, but I can enumerate some of them briefly:
- Constant expressions are supported with the constexpr keyword and they’re useful for performing more computations at compile time (rather than run time) without having to resort to less safe alternatives like macros.
- Uniform initilization using curly braces is a supposedly uniform way to initialize values without running into most-vexing parse problems that come from using parentheses. However, that “supposedly” should be a red flag — it’s not *that* uniform and the rules around how it works have changed repeatedly between C++11 and C++17 to try to make it more uniformish.
- Scoped enumerations and enumerations with fixed underlying types add more type safety to enumerations by fixing their underlying type (rather than letting the compiler pick a type as it sees fit), and also removes the need to put an enum into a namespace or class to ensure that enumerator names do not clash with other names.
- User-defined literals allow users to add their own literal suffixes to generate class literals. We’re all used to seeing things like
1.0f
and0u
, but UDLs extend these suffixes and the STL is starting to take advantage with more suffixes coming. - static_assert is a compile-time complement to the assert macro, allowing you to check conditions at compile time rather than run time.
- Binary literals (
0b0100101
) and digit separators (0xFFFFFFFF'FFFFFFFF
) - Variable templates allow the programmer to write variable declarations
using a template. For instance, it is now possible to declare a constant
floating-point value for pi whose precision depends on the underlying
type specified by the template. You can then use the correct instance of
the variable by specifyingpi<double>
orpi<float>.
- Structured bindings allow the programmer to decompose a structure (or structure-like container such as tuple) into constituent member variable access. e.g.,
struct S { int a, b; };
S get_s();
int main() {
auto [foo, bar] = get_s();
}
…and even more!
Conclusion
As you can see, the last three releases of C++ have added a considerable
number of language tools to the C++ programmer’s toolbox. Range-based
for loops change the way we iterate over containers, lambdas change the
way we write with algorithms, move semantics change the way we think
about memory management, and that’s just to name a few of the language
features of modern C++ before getting into the new standard library
features.