The second quirk is, to me, a much bigger issue and one that is meaningfully different between Concepts and CTAD and comes from exactly what problem it is that we’re trying to solve.

The motivation for CTAD as expressed in every draft of the paper is very much: I want to construct a specialization of a class template without having to explicitly specify the template parameters — just deduce them for me so I don’t have to write helper factories or look up what they are. That is, I want to construct a new thing.

The motivation for Concepts is broader, but specifically in the context of constrained variable declarations is: I want to construct an object whose type I don’t care about, but rather than using auto , I want to express a more specific set of requirements for this type. That is, I’m still using the existing type, I’m just adding an annotation.

At least that’s how I think about it.

These two ideas may not seem like they clash, but they do. And it may not appear that we’re making a choice between two things, but we are. This conflict is expressed by a recent twitter thread:

Never quit, JF. Never quit.

The issue boils down to: what does this do, exactly:

What is the intent behind the declaration of variable x ? Are we constructing a new thing (the CTAD goal) or are we using std::tuple as annotation to ensure that x is in fact a tuple (the Concepts goal)?

STL makes the point that most programmers would expect x and y to have the same meaning. But this kind of annotation wasn’t the goal of CTAD. CTAD was about creating new things — which suggests that while y is clearly a tuple<int> , x needs to be a tuple<tuple<int>> . That is, after all, what we’re asking for. We’re creating a new class template specialization based on our arguments?

This conflict becomes clearer in this example:

This is the point that Casey made. Is c a tuple<int> or a tuple<tuple<int>> ? Is z a vector<int> or a vector<vector<int>> ?

Today, if we’re using CTAD with a copy, the copy takes precedence. This means that the single-argument case effectively follows a different set of rules than the multi-argument case. Today, c is a tuple<int> and z is a vector<int> , each just copy-constructing its argument.

In other words, to Casey’s point, the type of tuple(args...) depends not only on the number of arguments but also their type. That is:

If sizeof...(args) != 1 : tuple<decay_t<decltype(args)>...>

: Otherwise, if args0 is not a specialization of tuple : tuple<decay_t<decltype(arg0)>>

is a specialization of : Else, decay_t<decltype(arg0)>

That’s decidedly not simple.

I think this is an unfortunate and unnecessary clash — especially in light of the imminent arrival of Concepts, that would allow us to easily distinguish between the two cases:

Here, we would use each language feature for the thing it does best: constructing new things for CTAD, and constraining existing things for Concepts.