Leaving C++03: deprecated standard features
Greetings.
This week, back to my series about taking QuantLib to C++11. I’ll tackle std::auto_ptr
and other features that were deprecated or removed in recent
standards. But first, a coda on automation.
clang-tidy and make_shared
In my last post on automation, I mentioned that the
modernize-make-shared
check didn’t work on our code base; it looks
for instances of std::shared_ptr
, but we’re hiding them inside a
namespace for backwards compatibility so we can choose either boost
or std
(details here).
With some encouragement, though, it can be made to work: I replaced
all ext::shared_ptr
with std::shared_ptr
(which, luckily, is a
one-line invocation of sed
from the command line), ran the check,
and replaced std::shared_ptr
and std::make_shared
with the same
classes in namespace ext
again. The check touched around 550 lines
in 120 files.
It turned out that a handful of changes had to be reverted, though.
First, users can choose to compile the code in C++03 mode, which means
no variadic templates. The implementation of boost::make_shared
(because that’s what we can use in this case) is hard-coded to take a
maximum of 9 arguments. Any invocations taking more than that had to
be reverted. Second, VC++10 didn’t compile a handful of cases in
which the result of make_shared
was used as a default argument, as
in that last argument of:
I didn’t bother finding out what was wrong, and I reverted the
changes. Finally, Visual C++ 2012 doesn’t really support variadic
templates, and make_shared
is written as a bunch of overloads. The
maximum number of arguments is defined in the value of the
_VARIADIC_MAX
macro, which by default is not nearly enough. I had to
define it to 9 so that it could support as many arguments as Boost.
A final note: the check doesn’t modernize all that it could modernize.
It will replace std::shared_ptr<Derived>(new Derived)
with
std::make_shared<Derived>()
, but it will not touch
auto_ptr and other deprecated features.
Onwards to the main attraction. Writing on automation I mentioned that, in our case,
the modernize-replace-auto-ptr check was—how can I say?—a dumpster
fire. However, it didn’t bother me; I ran the check for completeness,
but I hadn’t planned on replace std::auto_ptr
unconditionally.
Remember our context: we’re moving to C++11 on an experimental branch,
but our master is still using C++03 and is not making the switch in
the near future. Replacing auto_ptr
on the experimental branch
wouldn’t fix the fact that recent compilers have started issuing
deprecation warnings, and our master is affected by that. I wanted
to address this problem as
well.
This means providing the means to choose between std::auto_ptr
and
std::unique_ptr
at compile time, so that users can opt in to the
newer class when their compiler is ready. The way to provide the
switch is the usual one: a configure flag or a define, depending on
your build system. But how should we switch between the two classes
in the affected code?
If you follow Zach Holman on Twitter (bear with me for a moment if I seem to wander away from the topic at hand) you might have seen this:
Was talking recently with a friend about how hard it is to write blog posts and give talks these days. The longer you’re in the tech industry the more your opinions end up being “well it depends” and “I’m not sure”. These don’t quite make for good blog posts, hah.
— Zach Holman (@holman) January 11, 2018
(Incidentally, this also seems to puzzle younger colleagues coming to you with questions and expecting leadership—whatever that means.)
To return to the task at hand: should we define a macro that resolves
either to std::auto_ptr
or std::unique_ptr
, or should we be more
explicit and show the choice? The answer I came up with was, of
course, “it depends.”
In header files, which is where people look for a listing of the features of a class, I preferred to be more explicit and write, for instance,
instead of hiding the thing behind a macro that someone reading the code would have to look up. In source files, instead, the above gets old very quickly: something like
hurts readability so much it isn’t even funny. Alternatives like having two versions of the entire method are a bit better, but in the end I settled on defining a macro and writing
In a couple of cases, I had to go beyond a simple replacement. One
was a class with a constructor (among others) taking a std::auto_ptr
and storing it in a data member: something like
The alternate version using std::unique_ptr
gave us the first foray
into proper modern C++, using rvalue references and moves:
In the other tricky case, I tried to accommodate both kind of pointers and I ended up with something like this:
The final assignment didn’t compile: the copy constructor of
std::auto_ptr
, which is used to initialize the argument passed to
the Clone
constructor, takes a non-const reference—the passed
pointer has to be modified—and g++ couldn’t bind it to the rvalue
returned by clone()
. (Strangely enough, clang compiled the same
code without any problems, leaving me fairly puzzled.)
I could have stored b.clone()
in a variable so that I would pass an
lvalue to the Foo
constructor; but that wouldn’t have worked for
std::unique_ptr
. I ended up using another Clone
constructor that
takes a Bar
instance, as in:
The above works with both std::auto_ptr
and std::unique_ptr
and
also simplifies the code at the call site, so it turned out to be a
win after all.
As usual, your mileage may vary. You might have more bits of client
code that require different treatment of auto_ptr
and unique_ptr
,
so be ready to retouch your code manually.
Other features
std::auto_ptr
is not the only deprecated feature in C++11, although
the other ones don’t seem to give compilation warnings in gcc (on the
other hand, clang masquerading as gcc on Mac OS X removes them in
C++17 mode, so the code doesn’t compile). The ones used in our code
were std::bind1st
, std::bind2nd
, std::ptr_fun
, std::mem_fun
and (in one implementation file) std::random_shuffle
.
The binders would be replaced naturally by lambdas in C++11 and
beyond. However, as for std::auto_ptr
, I looked for a solution that
could also work in C++03 and be applied to the master branch.
The binders were deprecated in favor of std::bind
, but that’s not
available in C++11 either. However, it would have been possible to do
something similar to what I did with shared_ptr
: use an ext::bind
class that
could be mapped to boost::bind
or std::bind
depending on the
standard one is using.
Well, I tried that, but somehow it didn’t fly. Sure, it compiled, but in most cases what I was doing was changing 15-years old code like
into something like
which, in my old age, makes me cringe. Long story short, I made a note of revisiting this for lambdas and in the meantime I created a few reusable functors and wrote the above as
If anybody ever said to me in my youth that
std::bind2nd(std::minus<Real>(), 1.0)
wasn’t readable and if I went
“Well actually” on them, I am deeply sorry.
The other wrappers were simpler to replace. std::mem_fun
was used
in a couple of places with one of the binders, and was no longer
needed in the functors I used instead. std::ptr_fun
was mostly used
to disambiguate overloaded functions, as in
and could be replaced by a cast such as
Finally, std::random_shuffle
. Fortunately, it was used in a single
implementation file. There was nothing to replace it with in C++03
mode, so I used conditional compilation and wrapped it with:
The above switches to std::shuffle
for C++14 and above; in
principle, I could have looked for __cplusplus >= 201103L
(C++11)
but some older compiler might declare it and still have incomplete
support, so I played it safe.
So far, I didn’t find the time (or, indeed, the motivation) to research, install and moderate a comment system. If you want to reach out with suggestions or questions, please send me a tweet or an email. I’ll report on your feedback in future posts.
Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.