Highlights of ISO C++14
Voting on the C++14 standard was completed in August, and all that remains before we can say it is officially complete is publication by the ISO. In this article I will visit the high points of the new standard, demonstrating how the upcoming changes will affect the way you program, particularly when using the idioms and paradigms of Modern C++.
The committee seems intent on keeping the standards process in a higher gear than in the past. This means that C++14, having had just three years since the last standard, is a somewhat constrained release. Far from being disappointing, this is a boon for programmers, because it means implementers have been able to push out compliance with the new features in real time. Yes, you can start using C++14 features today. Nearly all if you are flexible on your tool chain.
At this point you can get a free copy of the draft proposal here. Unfortunately, when the final standard is published, ISO will have it paywalled.
Conformance
I think that shortening the time-frame between releases is working to help compiler writers keep up with the language changes in something closer to real-time. With just three years between releases, there are fewer changes to adjust to.
The examples in this article were mostly tested with clang 3.4, which has great coverage of C++14 features. g++ has a somewhat smaller list of features covered, and Visual C++ seems to be trailing the pack.
Interestingly, I actually did all my clang development on a ubuntu-14.04 LTS system hosted on Microsoft Azure, using my monthly MSDN credits. I’m never sure how much those free credits really mean, but I haven’t used them up yet, and it’s nice to experiment on a well-managed VM.
My first step after creating the VM and using puTTY for an SSH connection was to install Dropbox using the generic installer from the Dropbox site:
cd ~ && wget -O - "https://www.dropbox.com/download?plat=lnx.x86_64" | tar xzf - ~/.dropbox-dist/dropboxd
This gives me a URL I can use to authenticate this Dropbox account. I go to my browser to enable this VM as a legitimate user of my Dropbox account.
After that I want to use the Dropbox CLI script to control it, so I kill dropboxd
with CTRL-C, then:
wget -O ~/dropbox.py https://www.dropbox.com/download?dl=packages/dropbox.py chmod u+x ~/dropbox.py ~/dropbox.py start
I can now check up on the status with ~/dropbox.py status
, as my source files are linked to this computer.
Meanwhile, I need to install the components of clang that are going to give a 3.4 compatible workstation:
sudo apt-get update sudo apt-get install clang-3.4 sudo apt-get install libc++-dev sudo apt-get install binutils # create helloworld.cpp then test clang++ -stdlib=libc++ -std=c++1y helloworld.cpp
After installing clang-3.4, libc++, and binutils, a typical example is built using this command line on Linux:
clang++ -std=c++1y -stdlib=libc++ example.cpp
C++14 changes of note
What follows are descriptions of the C++14 changes that have significant impact in your life, along with working code and discussions of when and why you would employ them.
Return type deduction
The continuing expansion of auto
in the language is an interesting development.
C++ itself continues to be typesafe, but the mechanics of type safety are increasingly being
performed by the compiler instead of the programmer.
In C++11, programmers starting using auto
for declarations. This was keenly
appreciated for things like iterator creation, when the fully qualified type name might be
horrendous. Newly minted C++ code was much easier to read:
This code is still completely typesafe - the compiler knows what type begin()
returns
in that context, so there is no question about what type ii
is, and that will be
checked every place it is used.
In C++14, the use of auto
was expanded in a couple of ways. One that makes perfect
sense is that of return type deduction. If I write a line of code like this inside a function:
it is obvious to both me and the compiler that the function is returning a double. So in C++14, I
can define the function return type as auto
instead of double:
The details of this new feature are pretty easy to understand. For example, if a function has multiple return paths, they need to have the same type. Code like this:
might seem like it should obviously have a deduced return type of double
, but the
standard prohibits this ambiguity, and the compiler property complains:
error_01.cpp:6:5: error: 'auto' in return type deduced as 'double' here but deduced as 'int' in earlier return statement return 2.0 ^ 1 error generated.
There are a couple of good reasons why deducing the return type is a plus for your C++ programs.
First, there are times when you have to return a fairly complex type, such as an iterator, perhaps
when searching into a standard library container. The auto
return type makes the
function easier to write properly, and easier to read.
A second, maybe less obvious reason, is that using an auto
return type enhances your
ability to refactor. As an example, consider this program:
In this example, I’m not saving many brain cells by having find_id()
return
auto
instead of int
. But consider what happens if I decide that I want to
refactor my record
structure. Instead of using an integral type to identify the
person in the record
object, maybe I have a new GUID
type:
Making that change to the record
object will cause a series of cascading changes in
things like the return types of functions. But if my function uses automatic return type
deduction, the compiler will silently make the change for me.
Any C++ programmer who has worked on a large project is familiar with this issue. Making a change
to a single data structure can cause a seemingly endless series of iterations through the code
base, changing variable, parameter, and return types. The increased use of auto
does a lot to
cut through this bookkeeping.
Note:
In the example above, and in the rest of this article, I create and use a named lambda. I suspect
that most users of lambdas with functions like std::find_if()
will define their
lambdas as anonymous inline objects, which is a very convenient style. Due to limited page width,
I think it is a little easier to read code in your browser when lambdas are defined apart from their usage.
So this is not necessarily a style you should emulate, you should just appreciate that it is somewhat easier to read. In particular, it will be much easier if you are light on lambda experience.
An immediate consequence of using auto
as a return type is the reality of its
doppelganger, decltype(auto)
and the rules it will follow for type deduction. You can
now use it to capture type information automatically, as in this fragment:
Generic lambdas
Another place where auto
has insinuated itself is in the definitions of lambda
parameters. Defining lambda parameters with an auto
type declaration is the loose
equivalent of creating a template function. The lambda will be instantiated in a specific
embodiment based on the deduced types of the arguments.
This can be convenient for creating lambdas that can be reused in different contexts. In the simple example below, I’ve created a lambda used as a predicate in a standard library function. In the C++11 world, I would have needed to explicitly instantiate one lambda for adding integers, and a second for adding strings.
With the addition of generic lambdas, I can define a single lambda with generic parameters.
Although the syntax doesn’t include the keyword template
, this is still clearly a
further extension of C++ generic programming:
Which produces the following output:
Even if you are instantiating anonymous inline lambdas, the use of generic parameters is still useful for the reasons discussed earlier in this article. When your data structures change, or functions in your APIs get signature modifications, generic lambdas will adjust with recompilation instead of requiring rewrites:
Initialized lambda captures
In C++11 we had to start adjusting to the notion of a lambda capture specification. That declaration guides the compiler during the creation of the closure: an instance of the function defined by the lambda, along with bindings to variables defined outside the lambda’s scope.
In the earlier example on deduced return types, I had a lambda definition that captured a single
variable name
, used as the source of a search string in a predicate:
This particular capture gives the lambda access to the variable by reference. Captures can also be performed by value, and in both cases, the use of the variable behaves in a way that fits with C++ intuition. Capture by value means the lambda operates on a local copy of a variable, capture by reference means the lambda operates on the actual instance of the variable from the outer scope.
All this is fine, but it comes with some limitations. I think the one that the committee felt it needed to address was the inability to initialize captured variables using move-only semantics.
What does this mean? If we expect that a lambda is going to be a sink for a parameter, we
would like to capture the outer variable using move semantics. As an example, consider how you
would get a lambda to sink a unique_ptr
, which is a move-only object. A first attempt
to capture by value fails:
This generates a compiler error because unique_ptr
does not generate a copy
constructor - it specifically wants to ban making copies.
Changing this so that p
is captured by reference compiles fine, but it doesn’t have
the desired effect of sinking the value by moving the value into the local copy. Eventually you
could accomplish this by creating a local variable and calling std::move()
on your
captured reference, but this is a bit inefficient.
The fix for this is a modification of the capture clause syntax. Now instead of just declaring a capture variable, you can do an initialization. The simple case that is used as an example in the standard looks like this:
This captures a copy of x and increments the value simultaneously. This example is easy to understand, but I’m not sure it captures the value of this new syntax for sinking move-only variables. A use case that takes advantage of this shown here:
In this case, the captured value p
is initialized using move semantics, effectively
sinking the pointer without the need to declare a local variable:
inside: 11 Segmentation fault (core dumped)
That annoying result is what you expect - the code attempts to dereference p
after it
was captured and moved into the lambda.
The [[deprecated]] attribute
The first time I saw the use of the deprecated attribute in Java, I admit to a bit of language envy. Code rot is a huge problem for most programmers. (Ever been praised for deleting code? Me neither.) This new attribute provides a systematic way to attack it.
Its use is nice and simple - just place the [[deprecated]]
tag in front of a
declaration - which can be a class, variable, function or a few other things. The result looks
like this and looks like this:
When your program uses a deprecated entity, the compiler’s reaction is left up to the implementer. Clearly most people are going to want to see some sort of warning, and most likely be able to turn that warning off at will. As an example, clang 3.4 gave this warning when instantiating a deprecated class:
dep.cpp:14:3: warning: 'flaky' is deprecated [-Wdeprecated-declarations] flaky f; ^ dep.cpp:3:1: note: 'flaky' declared here flaky { ^
Note that the syntax of C++ attribute-tokens might seem a bit unfamiliar. The list of
attributes, including [[deprecated]]
, comes after keywords like class
or
enum
, and before the entity name.
This tag has an alternate form that includes a message parameter. Again, it is up to the implementer to decide what to do with this message. clang 3.4 apparently ignores the message. The output from this fragment:
does not contain the error message:
dep.cpp:14:10: warning: 'cranky' is deprecated [-Wdeprecated-declarations] return cranky(); ^ dep.cpp:6:5: note: 'cranky' declared here int cranky() ^
Binary literals and digit separators
These two new features aren’t earth-shaking, but they do represent nice syntactic improvements. Small changes like these give us some incremental improvements in the language that improve readability and hence reduce bug counts.
C++ programmers can now create binary literals, adding to the existing canon of decimal, hex, and
the rarely used octal radices. Binary literals start with the prefix 0b
and are
followed by binary digits.
In US and UK, we are used to using commas as digit separators in written numbers, as in: $1,000,000. These digit separators are there purely for the convenience of readers, providing syntactic cues that make it easier for our brains to process long strings of numbers.
The committee added digit separators to C++ for exactly the same reasons. They won’t affect the evaluation of a number, they are simply present to make it easier to read and write numbers through chunking.
What character to use for a digit separator? Virtually every punctuation character already has an
idiosyncratic use in the language, so there are no obvious choices. The final election was to use
the single quote character, making the million dollar value render in C++ as:
1'000'000.00
. Remember that the separators don’t have any effect on the evaluation of
the constant, so this value would be identical to 1'0'00'0'00.00
.
An example combining the use of both new features:
This program gives the unsurprising output:
Output mask: 33152 Proposed salary: $300000
The remainder
Some additional features in the C++14 specification don’t require quite as much exposition.
Variable templates are an extension of templates to variables. The example used everywhere is an
implementation of variable pi<T>
. When implemented as a double, the variable will
return 3.14, when implemented as an int, it might return 3, and “3.14” or perhaps “pi” as an
std::string
. This would have been a great feature to have when <limits>
was being written.
The syntax and semantics of variable templates are nearly identical to those for class templates - you should have no trouble using them without any special study.
The restrictions on constexpr
functions have been relaxed, allowing, for example,
multiple returns, internal case
and if
statements, loops, and more.
This expands the scope of things that are done at compile time, a trend that really took wing
when templates were introduced.
Additional minor features include sized deallocations and some syntax tidying.
What next?
The C++ committee clearly feels pressure to keep the language current through improvements, and is already working on at least one more standard in this decade, C++17.
Possibly more interesting is the creation of a number of spin-off groups that can create technical specifications, documents that won’t rise to the level of a standard but will be published and endorsed by the ISO committee. Presumably these can be issued at a more rapid clip. The eight areas currently being worked include:
-
File system
Concurrency
Parallelism
Networking
Concepts (the AI of C++ - always one round of specification away)
Success of these technical specifications will have to be judged by adoption and use. If we find that all the implementers line up behind them, then this new track for standardization will be a success.
C/C++ has held up well over the years. Modern C++, which we might mark as starting with C++11, has taken dramatic strides in making the language easier to use and safer without making concessions in the areas of performance. For certain types of work it is hard to think of any reasonable alternative to C or C++. The C++ 14 standard doesn’t make any jumps as large as that in the C++11 release, but it keeps the language on a good path. If the committee can keep its current level of productivity for the rest of the decade, C++ should continue to be the language of choice when performance is the guiding force.