1

C++20 introduces concepts, which allows us to specify in the declaration of a template that the template parameters must provide certain capabilities. If a template is instantiated with a type that does not satisfy the constraints, compilation will fail at instantiation instead of while compiling the template's body and noticing an invalid expression after substitution.

This is great, but it begs the question: is there a way to have the compiler look at the template body, before instantiation (i.e. looking at it as a template and not a particular instantiation of a template), and check that all the expressions involving template parameters are guaranteed by the constraints to exist?

Example:

template<typename T>
concept Fooer = requires(T t)
{
  { t.foo() };
};

template<Fooer F>
void callFoo(F&& fooer)
{
  fooer.foo();
}

The concept prevents me from instantiating callFoo with a type that doesn't support the expression that's inside the template body. However, if I change the function to this:

template<Fooer F>
void callFoo(F&& fooer)
{
  fooer.foo();
  fooer.bar();
}

This will fail if I instantiate callFoo with a type that defines foo (and therefore satisfies the constraints) but not bar. In principal, the concept should enable the compiler to look at this template and reject it before instantiation because it includes the expression fooer.bar(), which is not guaranteed by the constraint to exist.

I assume there's probably backward compatibility issues with doing this, although if this validation is only done with parameters that are constrained (not just typename/class/etc. parameters), it should only affect new code.

This could be very useful because the resulting errors could be used to guide the design of constraints. Write the template implementation, compile (with no instantiations yet), then on each error, add whatever requirement is needed to the constraint. Or, in the opposite direction, when hitting an error, adjust the implementation to use only what the constraints provide.

Do any compilers support an option to enable this type of validation, or is there a plan to add this at any point? Is it part of the specification for concepts to do this validation, now or in the future?

user7610
  • 25,267
  • 15
  • 124
  • 150
sweatervest
  • 143
  • 8
  • 1
    Duplicate of [How do I statically check my templated class at definition time?](https://stackoverflow.com/questions/59335619/how-do-i-statically-check-my-templated-class-at-definition-time) – Jason Jun 27 '22 at 17:20
  • Why do you care whether the compiler error happens before or after instantiation? – Spencer Jun 27 '22 at 18:03
  • 1
    @Spencer Because if it happens _before_ instantiation, the template author can fix the bug. If it happens _during_ instantiation, it's the user who gets the error on a template that might be an internal implementation detail of some library that they have no control over. It's _much_ better to get errors earlier (for the same reason that a compile error is better than a runtime error). – Barry Jun 27 '22 at 18:14

2 Answers2

2

Do any compilers support an option to enable this type of validation, or is there a plan to add this at any point? Is it part of the specification for concepts to do this validation, now or in the future?

No, no, and no.

The feature you're looking for is called definition checking. That is, the compiler checks the definition of the template at the point of its definition based on the provided concepts, and issues errors if anything doesn't validate. This is how, for instance, Rust Traits, Swift Protocols, and Haskell Typeclasses work.

But C++ concepts don't work like that, and it seems completely infeasible to ever add support for such a thing given that C++ concepts can be arbitrary expressions rather than function signatures (as they are in other languages).

The best you can do is thoroughly unit test your templates with aggressively exotic types that meet your requirements as minimally as possible (the term here is archetype) and hope for the best.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • That sounds more like a job for a static analyzer -- It could generate an archetype based on concepts and requires clauses in the code and try to compile that. – Spencer Jun 27 '22 at 18:38
  • @Spencer Well, that is what the compiler does - in the languages that support this feature. It's just that in C++ this basically infeasible. The issue here also isn't just _checking_ - it's coercing. If you have a `predicate F`, I don't want to have to `(bool)f(42)` everywhere, since all I know is that this thing is convertible to `bool` and not that it is _is_ `bool` -- C++0x concepts did that for us too. – Barry Jun 27 '22 at 19:45
  • This is a good answer, but I accepted the other one because it gave a concrete example of the infeasibility. As a developer who's already become deeply familiar with how different C++ templates are from generics in other languages like some you mentioned, but who's brand new to concepts, I'm now learning that concepts are also really quite different than generic constraints. – sweatervest Jul 03 '22 at 16:04
1

TL;DR: no.

The design for the original C++11 concepts included validation. But when that was abandoned, the new version was designed to be much more narrow in scope. The new design was originally built on constexpr boolean conditions. The eventual requires expression was added to make these boolean checks easier to write and to bring some sanity to relationships between concepts.

But the fundamentals of the design of C++20 concepts makes it basically impossible to do full validation. Even if a concept is built entirely out of atomic requires expressions, there isn't a way to really tell if an expression is being used exactly in the code the way it is in the requires expression.

For example, consider this concept:

template<typename T, typename U>
concept func_to_u = requires(T const t)
{
  {t.func()} -> std::convertible_to<U>;
};

Now, let's imagine the following template:

template<typename T, typename U> requires func_to_u<T, U>
void foo(T const &t)
{
  std::optional<U> u(std::in_place, t.func());
}

If you look at std::optional, you find that the in_place_t constructor doesn't take a U. So... is this a legitimate use of that concept? After all, the concept says that code guarded by this concept will call func() and will convert the result to a U. But this template does not do this.

It instead takes the return type, instantiates a template that is not guarded by func_to_u, and that template does whatever it wants. Now, it turns out that this template does perform a conversion operation to U.

So on the one hand, it's clear that our code does conform to the intent of func_to_u. But that is only because it happened to pass the result to some other function that conformed to the func_to_u concept. But that template had no idea it was subject to the limitations of convertible_to<U>.

So... how is the compiler supposed to detect whether this is OK? The trigger condition for failure would be somewhere in optional's constructor. But that constructor is not subject to the concept; it's our outer code that is subject to the concept. So the compiler would basically have to unwind every template your code uses and apply the concept to it. Only it wouldn't even be applying the whole concept; it would just be applying the convertible_to<U> part.

The complexity of doing that quickly spirals out of control.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • In the end, it's incumbent on the person designing function `foo` to analyze the requirements imposed by the function body. `requires func_to_u` is there because of the use of `optional`, not the other way around. – Spencer Jun 27 '22 at 17:42
  • Thank you. The concrete example of why definition checking is infeasible is really helpful. – sweatervest Jul 03 '22 at 16:03