if requires: the concepts-based if constexpr

412 views
Skip to first unread message

Nicol Bolas

unread,
Jun 25, 2016, 2:56:41 PM6/25/16
to ISO C++ Standard - Future Proposals
Concepts introduces the notion of the `requires` clause: a boolean expression. C++17 will likely introduce if constexpr, which is a condition based on a constant expression.

Since requirements are by definition constexpr, it makes sense to be able to combine the two with:

if requires(...) {...}

Now yes, `requires` clauses are `constexpr`, so it would be possible as is to use them with `if constexpr`:

if constexpr (requires(...) {...})

But there's no need to say the same thing multiple times. With concepts and `if constexpr`, I can very easily see people wanting to use them in tandem, so having shorter syntax is not unreasonable. This is similar to how `concept` also implies `constexpr`, without us explicitly having to say it.

Consider the visual difference between:

if requires(T a) {a.SomeFunc();}

and

if constexpr(requires(T a) {a.SomeFunc();})

One useful thing here is that we often already have the equivalent of `T a` somewhere in the program, so most uses of `if requires` would just have an empty parameter list.

Note that `if requires` would have semantic differences from `if(requires{...})`. The latter is a regular `if` statement that just so happens to use a `requires` expression. The former follows the rules of a true `if constexpr`.

This feature could also be used to give us a way of dealing with the definition checking issue. One of the issues with definition checking is that template implementations that are governed by a concept sometimes want to have optional components. If a type has some function, then it will call it, but types which don't have that function are OK as well.  Such things are not listed in the concept's constraints because it is something which all types can fulfill. That is, there is no actual constraint requirement; it is a semantic constraint rather than a syntactic one.

The usual way of definition testing is to match the expressions in the constraint with the dependent expressions used in the template. Those which don't match the constraint requirements are illegal. This would prevent the use of such optional features, since by definition they would not be listed in the constraint requirements.

By wrapping such expressions in an `if requires` clause, we're effectively saying that we are adding to the list of expressions that we permit within the template. The reason being that the conditional makes sure that the implementation provides an alternative if the functionality is not present. So if you want to log certain information, and your `Log` function is intended to accept any type, you would do this:

if requires() {Log(a);}
{
 
Log(a);
}

If it turns out that someone provides some type that uses specializations to prevent `Log` from being called on it, then the `requires` clause will be false and the function will not be called. If you want that to be a compile error, you can still do so:

if requires() {Log(a);}
{
 
Log(a);
}
else
 
static_assert(false, "Why did you do that?");

We could improve this by making it require less repetition:

if requires
{
 
Log(a);
}

Since a `requires` expression normally needs a parameter list, the use of `if requires` without such a list has special meaning. It should mean that all of the (dependent) expressions in the `if` statement body should be considered part of the `requires` clause. Therefore, if any of those requirements are not met, the `requires` clause yields `false`, and the `else` clause triggers (if any).

Zhihao Yuan

unread,
Jun 25, 2016, 3:21:57 PM6/25/16
to std-pr...@isocpp.org
On Sat, Jun 25, 2016 at 1:56 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> Consider the visual difference between:
>
> if requires(T a) {a.SomeFunc();}
>
> and
>
> if constexpr(requires(T a) {a.SomeFunc();})
>

#define required(expr, ...) constexpr (requires(__VA_ARGS__) { expr; })

if required(a.SomeFunc(), T a)
do_something();
if required(Log(a))
Log(a);

// gcc -E
if constexpr (requires(T a) { a.SomeFunc(); })
do_something();
if constexpr (requires() { Log(a); })
Log(a);

> else
> static_assert(false, "Why did you do that?");

Not supported in C++17. Papers fixing static_assert's
interaction with constexpr if are welcome.

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://blog.miator.net/

Tom Honermann

unread,
Jun 26, 2016, 6:03:27 PM6/26/16
to std-pr...@isocpp.org
This idea came up previously [1], but to my knowledge has not been discussed as thoroughly as you presented here.  The following example was provided in that email thread:

template<InputIterator I>
I advance(I i, int n) {
  if requires(RandomAccessIterator<I>()) {
    i += n;
  } else {
    while (n > 0)
      ++i;
  }
}

The above does use a different syntax then what you proposed.  Rather than using a requires expression with the if statement, the above uses a requires clause.  I find the above more natural, though it does open the door to cases of 'requires requires' if one wants to add requirements based on arbitrary expressions rather than on a constraint-expression.

I agree with your presentation of the semantic differences between 'if requires' and 'if constexpr(requires {})'.  In particular, the former is explicit that a constraint is being added to (a portion of) the function definition and, in a definition checking future, it seems reasonable to then restrict uses of expressions that would otherwise fail a constraint check to the body of the if statement.  In the latter case, it is not obvious to me whether such expressions could be used in the 'if constexpr' condition (e.g., in a conjunction following the requires expression), or would be restricted to the statement body.  I would expect a compiler's ability to reason about the constraints to be simpler for the former case as well.

I'm not sure about the idea to implicitly allow any expressions used in the statement body if the 'if requires' doesn't explicitly list constraints.  It seems like that would require speculative instantiation which, as I understand it, would be difficult for at least some implementors.

I do like this approach and have been intending to write a paper about it, but time and energy...  I think a feature like this is necessary for template definition checking to become a reality with the current Concepts proposal.

Tom.

[1]: On the EWG reflector, email thread titled "constexpr if and concept definition checking", 2016-02-26.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/97c876f2-bde5-4555-a18d-3bae8c4abc24%40isocpp.org.

Reply all
Reply to author
Forward
0 new messages