Contra CTAD

Today Andrzej Krzemieński posted a really nice blog post on class template argument deduction (CTAD) and std::optional. The key example from “Deducing your Intentions” (2018-12-09) goes like this:

template<class T>
void f1(T v)
    auto o = std::make_optional(v);
    static_assert(std::is_same_v<decltype(o), std::optional<T>>);

template<class T>
void f2(T v)
    auto o = std::optional(v);
    static_assert(std::is_same_v<decltype(o), std::optional<T>>);

The static_assert in f1 will always pass (as long as T is deduced; I mean obviously f1<int&> will fail the assertion).

The static_assert in f2 will sometimes pass and sometimes fail. For example,

std::optional<int> o;
f1(o);  // OK
f2(o);  // static_assert fails

Read Andrzej’s excellent post for the full details.

At CppCon 2018, Stephan T. Lavavej gave a pretty great talk on CTAD: “Class Template Argument Deduction for Everyone.” Stephan’s point of view is essentially “pro-CTAD,” but he revels in the subtle details just as much as I do. (He has to; you can’t write a solid library implementation unless you know all the crazy corner cases users might throw at you.) So, by the end of his talk, I was even more convinced than before that CTAD is a feature best avoided in production codebases.

I asked whether any vendors were moving to diagnose any use of CTAD under a command-line flag. This is a reaction we’ve seen to previous decades’ features which also fell into the bucket of “possible to use safely, but not safe per se”; I’m thinking specifically of -fno-exceptions and -Wvla.

ARTHUR: I came in here thinking that CTAD was terrible and I never want to use it, and you convinced me that I was right.

Are any of the major compilers considering adding a diagnostic for unintentional use of CTAD?

STEPHAN: Ah… how can we determine what is unintentional? You said you wanted a pair, and we constructed a pair, and it succeeded! How do we know you didn’t actually want that?

ARTHUR: Well, because we turned on the warning.

STEPHAN: Oh, turned on a warning. That’s—

ARTHUR: We don’t want to write [your pair-like example] mp2… and I am perfectly willing to throw out the baby with the bathwater.

STEPHAN: So, what if somebody doesn’t like uniform initialization, and they want a warning, “Wow, you used braces there, I don’t think you wanted that”? Or, “wow, you used nullptr, but we were trying to use the NULL macro”?

I mean, a compiler could implement a warning. You could probably go implement it in Clang right now. But that doesn’t seem to be a very useful warning to me, because, what is the danger of using CTAD? It could deduce the wrong type? That seems unlikely. I have not encountered cases where CTAD, used with the STL, will deduce wrong types. Like, the MyPair example here might seem scary; but std::pair has a deduction guide, because we thought about this.

So, I mean, yeah. The feature may do some unexpected things. But it seems fairly low-risk. Certainly much lower risk than the usual sorts of things the compiler emits warnings for. But yeah— submit a pull request to Clang, see if you get it accepted.

As I explained to Richard Powell afterward, “I like features that work 100% of the time. I hate features that work 99% of the time. Working 99% of the time is much worse than working 50% of the time.” And CTAD is the poster child for a feature that works 99% of the time. That is, it works 100% of the time… until it doesn’t.

As of a few days ago, I have submitted pull request D54565 to Clang. With Andrzej’s blog post landing today, it seems like an opportune time to mention that this pull request does exist, and I would very much like to see it get in!

As originally submitted, D54565 merely took the existing diagnostic (part of -Wc++14-compat) and split it out into a sub-diagnostic -Wc++14-compat-ctad that could be toggled individually. I got some feedback from which I inferred it might be more palatable as a plain old -Wctad, similar to the existing -Wvla for C99.

Of course, “more palatable” doesn’t necessarily mean “palatable.” It remains to be seen whether anyone in the Clang organization shares my worrywarting about CTAD, versus Stephan’s (and, clearly, the C++17 committee’s) position of

What is the danger of using CTAD? […] You said you wanted a pair, and we constructed a pair, and it succeeded! How [does the compiler] know you didn’t actually want that?

Yeah, what could possibly go wrong?

Posted 2018-12-09