Greetings.

In this post, I report on the clang-tidy checks I still hadn’t tried as of last post.

It wasn’t pretty. Spoiler alert: trying them ultimately resulted in filing three bugs in the clang-tidy tracker. The final summary is as follows: the files column contains the number of files touched, the lines column contains the number of lines touched, compiles shows whether or not the code compiled after the modernization, and fails PR merges shows if any of my sample set of pull requests (see this post) failed to merge against the modernized code.

files lines compiles fails PR merges modernize-pass-by-value 820 3377 no 8/43 modernize-use-override 788 3302 no 11/43 modernize-use-auto 260 485 yes modernize-loop-convert 211 3013 warnings 2/43 modernize-use-using 183 582 no modernize-use-equals-default 182 237 yes 1/43 modernize-use-nullptr 149 243 no modernize-replace-auto-ptr 95 167 no 2/43 modernize-use-emplace 87 1724 yes modernize-use-default-member-init 46 126 yes 1/43 modernize-return-braced-init-list 15 39 warnings modernize-redundant-void-arg 4 5 yes modernize-use-equals-delete 3 4 no modernize-raw-string-literal 3 3 yes modernize-use-noexcept 2 3 yes modernize-deprecated-headers 2 2 yes modernize-replace-random-shuffle 1 20 yes modernize-make-shared 0 0 modernize-shrink-to-fit 0 0 modernize-use-bool-literals 0 0 modernize-avoid-bind 0 0

A few available checks are not included in the table because they target a later C++ standard: modernize-make-unique and modernize-use-transparent-functors need C++14, and modernize-unary-static-assert needs C++17.

Some other checks (you can see them in the last rows of the table) didn’t find anything to modernize. For a couple of them, this was expected: modernize-avoid-bind works on uses of std::bind , whereas our C++03 code uses boost::bind , and modernize-make-shared works on std::shared_ptr , also not present in our legacy code. Replacing the Boost facilities with their std counterparts would make the checks useful; but I didn’t try the conversion at this time.

The results of the remaining checks were, let’s say, less than satisfactory.

Trying modernize-replace-auto-ptr caused compilation errors, due to a clang-tidy bug which I dutifully filed. Long story short: we have a class Clone defined as

template < class T > class Clone { public : Clone ( std :: auto_ptr < T > ); // other constructors, methods, etc. }; template < class T > inline Clone < T >:: Clone ( std :: auto_ptr < T > p ) : ptr_ ( p ) {}

and clang-tidy replaced std::auto_ptr with std::unique_ptr in the declaration of the constructor but not in the out-of-class definition. Hilarity ensued.

The good news is that I wasn’t planning to rely on this check anyway. Yes, it looks like a case of sour grapes. Lately, though, auto_ptr has been causing deprecation warnings in the original code and I’ve been thinking of conditionally replace it; you can read the details in this GitHub issue. Any solution to that would supersede the changes brought by this check and make the bug moot.

Running modernize-pass-by-value resulted in filing another clang-tidy bug. The idea of the check is to replace idiomatic C++03 code like

class Foo { public : Foo ( const Bar & b ) : b_ ( b ) {} private : Bar b_ ; };

with its modern C++ equivalent

class Foo { public : Foo ( Bar b ) : b_ ( std :: move ( b )) {} private : Bar b_ ; };

which avoids copies entirely when the passed object is a temporary one. Unfortunately, as for the previous check, we had cases in which the declaration of the constructor was modified correctly but the definition wasn’t.

Next was modernize-use-using. It replaces typedef statements with the modern using syntax; for instance,

typedef Sample < Real > sample_type ;

becomes

using sample_type = Sample < Real > ;

How did it go? Let me put it this way: while filling the table at the beginning of the post, I briefly considered writing “Heck no” in the compiles column.

Due to another clang-tidy bug, sometimes template parameters in a typedef were replaced with their instantiation; for instance, the typedef in

template < class Container > class Foo { public : typedef typename Container :: const_iterator const_iterator ; };

would sometimes become

template < class Container > class Foo { public : using const_iterator = typename map < int , double , less < int > , allocator < pair < const int , double > > >:: const_iterator ; };

instead of the correct

template < class Container > class Foo { public : using const_iterator = typename Container :: const_iterator ; };

I fixed a few declarations manually, but I threw the towel after spending ten minutes without finding the problem on a particularly hairy template instantiation. Also, for some reason some typedefs were matched by the check, the corresponding warnings was emitted, but the declaration was not converted. I was already bummed by that time, and didn’t try to figure out this one.

In any case, even if the check had worked, it wouldn’t have been the end of the story for using . The check only runs on existing typedefs, but the real new feature is that using gives you the possibility to have the equivalent of template typedefs, missing in C++03; that is, you can write things like

struct Discount { template < class T > using curve = InterpolatedDiscountCurve < T > ; }; class C : public Discount :: curve < Linear > {};

where in C++03 you have to resort to the clumsier

struct Discount { template < class T > struct curve { typedef InterpolatedDiscountCurve < T > type ; }; }; class C : public Discount :: typename curve < Linear >:: type {};

I’ll probably expand on the above in a future post, to show how we could use the using syntax in QuantLib (hint: the above is one such case); but anyway, the transformation is a breaking change and thus outside the scope of clang-tidy.

The modernize-loop-convert check was smarter than I thought. In absence of other information, it reuses for the loop variable the name of the original one; that is,

for ( Size i = 0 ; i < basket . size (); ++ i ) basket [ i ] -> setPricingEngine ( swaptionEngine );

becomes

for ( auto & i : basket ) i -> setPricingEngine ( swaptionEngine );

However, if the name of the container is plural, the check detects it and uses its singular for the name of the variable: for instance,

for ( Size i = 0 ; i < means . size (); ++ i ) std :: cout << means [ i ] << "

" ;

becomes

for ( double mean : means ) std :: cout << mean << "

" ;

(This is not without glitches, though. Ever the trickster, I tried an array named benches and it caused the loop variable to be named benche . On the other hand, I also tried a case in which a variable with the singular name was already defined, and the check correctly chose a harmless i for the name of the loop variable.)

In one case, the check even detected and reused a name that I had given to the element used in the iteration; that is,

for ( Size i = 0 ; i < callabilityTimes_ . size (); i ++ ) { Time exerciseTime = callabilityTimes_ [ i ]; // use exerciseTime }

became

for ( double exerciseTime : callabilityTimes_ ) { // use exerciseTime }

Both of the above are nice touches, so kudos to the developers. The only complaint I have is that the changes added a couple of warnings to the compilation, and with my usual -Wall -Werror flags this caused the build to fail. The original code was

Datum nominalData [] = { // data }; const Size nominalDataLength = 29 ; // the size of the array for ( Size i = 0 ; i < nominalDataLength ; i ++ ) { // use nominalData[i] }

and the check was smart enough to figure out that the loop spanned the whole array, therefore it turned it into

Datum nominalData [] = { // data }; const Size nominalDataLength = 29 ; // the size of the array for ( auto & i : nominalData ) { // i }

Unfortunately, the nominalDataLength variable was no longer used, hence the warning.

Finally, I don’t have a lot to say about modernize-return-braced-init-list, except that in one case the transformed code issued a warning. It turned the line

return Date (( 1 + dayOfWeek + skip * 7 ) - first , m , y );

into

return {( 1 + dayOfWeek + skip * 7 ) - first , m , y };

which caused a warning about narrowing the first of the returned constructor arguments.

So, what’s the verdict at the end of these three posts, apart from “most checks worked, some failed”?

In view of continuing the experiment (and possibly turn it in the future into a C++11 branch continuously integrating with master), I had hoped that the checks could be fully automated. This is not the case. True, some of the failures I described can be avoided by refactoring the original code; and if it came to that, others could be prevented by marking the corresponding lines so that clang-tidy doesn’t touch them. As a whole, though, the tool seems to me like the original Saturday Night Live crew: not ready for prime time. Let me stress it: this applies to our specific code base, and it might work perfectly for others. Try it out for yourselves.

However, clang-tidy remains a great tool to jump-start the process of modernization: the manual changes that need to be done after it runs are a lot less work than what you would have to do without the tool. And again, you might find that your code doesn’t need any.

A final note: as I write this, Ubuntu Bionic is out and includes version 6.0 of clang-tidy. Its release notes don’t seem to list changes that affect what I observed; but then again, I can’t exclude them. I’ll be checking it against the bugs I reported.

I’m not yet sure how this series will continue. I’ll probably try and consolidate in a single branch the changes I did so far; after which I might either add some tooling around it for continuous integration with master, or start monkeying around with other C++11 features. We’ll see what happens. In any case, I’ll keep you in the loop.

Follow me on Twitter if you want to be notified of new posts, or subscribe via RSS or email: the buttons for that are in the footer. Also, I’m available for on-site training in Europe and UK: visit my Training page for more information.