But whose real motivation is to allow:
That is, to allow a rich type hierarchy for reflection while still getting all the benefits that the monotype
info API is able to provde.
The way this is intended to work, from the initial paper, is described as:
Overload resolution in C++ happens at compile time, not run time, so how could this ever work? Consider the call to
pow in the following function:
Here the compiler knows at compile time that the second argument to
2 so it can theoretically make use of the overload with the parameter constraint. In what other cases does the compiler know at compile time the value of a parameter? As it turns out, we already have standardese for such an argument (or generally an expression) in C++: constant expression.
In short, this concepts extension will allow for parameter identifiers to appear in requires clauses and during overload resolution:
I think this proposal has a few problems.
The fundamental problem is that whether or not an expression is a constant expression is an ephemeral property of an expression. It has a tendency to not last as long as you want it to. Relying on an expression being a constant expression is going to prevent a whole class of abstractions using normal programming models.
Let’s just start with the
pow example. We had:
Cool. What if what we really wanted was
be + 1? No problem, we just write a new overload:
Right, we can’t wrap, because once we get to the body we don’t have constant expressions anymore. Likewise, we cannot even name the other
And the only way to properly wrap
pow is to actually manually write:
Just kidding. That’s still wrong! We have to actually write:
Think about how we might abstract if our constraint was more involved than a simple
Let’s go back to what really motivated this feature. This can work fine:
reflexpr(some_class) is a constant expression. Indeed, even this can work fine:
i is also a constant expression. But what happens when we try to use other library features:
push_back succeeds because it takes a
class_info&&, so the conversion happens while our expression is still a constant expression. But
emplace_back fails because it deduces its parameter to
info&& and has to perform the construction of
class_info internally, at which point our object is no longer a constant expression.
The general problem here is that the conversion has to happen right away, before we pass any function boundaries. If we stay as an
info for too long, we lose all ability to make these conversions:
This idea is reminiscent of another language feature we have: the fact that the literal zero is a null pointer constant. But since the type of the literal zero is still
int, this vanishes quickly:
Which presents very similar problems with forwarding:
There’s also a similar preexisting language feature with regards to narrowing:
But while the construction of
s is narrowing, it is at least possible to construct
s in a different way. This suggests that we would at least need to add a “back-up” conversion mechanism from
The proposal at hand introduces the notion of value-based overloading, but everything else in the language and library only ever deal with type-based overloading.
constructible_from<meta::class_info, meta::info> yield? By the rules laid out in these papers, it would yield
false. Except sometimes, it actually is constructible - but only from specific values, and only in specific situations.
Dealing with these types properly ends up requiring their own little shadow library; we’d have our normal concepts for types and then our function concepts for reflection.
Also, what would this mean:
For normal (type-based) concepts, this means
requires is_class<decltype(I)>. But that’s ill-formed for these new function concepts, it would have to mean
requires is_class(I), if anything. Which means we’d have to make a choice of either not having a terse syntax for this case or having a terse syntax have different semantics from other, similar-looking terse syntax.
Everyone trying to do something during constant evaluation will eventually try to do something to the effect of:
And be surprised that this fails, even though the function is
constexpr, even though the argument is a constant expression. And so we have to repeat the mantra over and over that function parameters are never constant expressions. Function parameters are never constant expressions.
Except, suddenly, with this paper, they can be. But only in a
requires clause. This adds more wrinkles into an already very-complex model that just makes it harder to understand.
Function parameter constraints is a creative and interesting compromise to trying to have both a monotype and a rich class hierarchy, but it presents its own problems that neither of the original choices had - and I think it has the potential to lead to a ton more confusion.
I am not sure that these problems are solvable without much more involved language changes, so in light of wanting reflection sooner rather than later, I think we should reconsider the direction of constrained function parameters.