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] << "\n";

becomes

    for (double mean : means)
        std::cout << mean << "\n";

(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.

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.