Why does C++ have no std::invocable_r concept?
Asked Answered
V

1

13

C++20 added concepts, and the standard library includes quite a few of them. One concept in particular caught my eye: std::invocable which validates that a functor can be invoked with a set of arguments.

std::invocable is just syntactic sugar for std::is_invocable. However, the standard library further defines std::is_invocable_r which tests whether a functor can be invoked with a set of arguments, but also that it returns a certain type once invoked. There are also nothrow versions of both of these utilities. However, there are no equivalent concepts defined in the standard.

Is there a reason the standard does not define these concepts, or was it just an oversight? Is there perhaps some minutia that a casual reader did not pick up on that made the committee decide not to include these?

Veronikaveronike answered 25/9, 2023 at 11:28 Comment(1)
std::is_invocable_r doesn't test that a functor returns a specific type R. It tests that the return type is convertible to R. I suspect the rationale here would be the same for having std::same_as and std::convertible_to in the final design of concepts. I.e. both are valid, so the standard doesn't arbitrarily pick one, letting us choose when needed.Monogamist
J
14

The design approach of the concepts library is much more minimalistic than that of type traits. For example, there is no std::arithmetic concept to match the std::is_arithmetic trait. This has two reasons:

  1. It can be trivially constructed from std::is_arithmetic, or from std::integral and std::floating_point.
  2. It is unclear which of these constructions should be preferred.

See also: Will there be a concept for arithmetic types in C++ standard library?

Issues with std::invocable and convenience concepts in general

A similar problem exists with std::invocable and its _r and nothrow variants. You can trivially construct a concept like this:

template <typename T, typename R, typename... Args>
concept invocable_r = invocable<T, Args...> && requires (Args&&... args) {
    { invoke(forward<Args>(args...)) } -> convertible_to<R>;
};

However, it is not so clear that this is the definitive implementation. You could also construct it in terms of std::invoke_r, and it's unclear whether std::convertible_to should still be used then.

In summary, the concepts library does not include concepts with the following issues:

  1. The concept can be easily constructed from others, so it is merely there for convenience. This includes nothrow variants, _r variants, disjunctions like std::arithmetic etc. The user can just make these themselves.

  2. There are multiple possible implementations, and it isn't perfectly clear which one should make it into the standard library. Keep in mind that the exact way a concept is defined can make it more constrained than another concept, so implementation details matter.

Note that concepts standardization was largely driven by the ranges library, and std::invocable_r is not essential to constraining ranges. std::invoke is used by std::indirect_result_t in std::indirectly_writable.

The difficulty of standardizing a proposal

Last but not least, remember that standardizing any language feature is a difficult task. The slimmer a proposal, the easier it is to get it through the committee. If someone made a proposal nowadays that provides such convenience concepts, there is a good chance that it would find success, however, it would have been a difficult task back in the day which would have increased the size of the proposal (P9898: Standard Library Concepts) considerably.

Jerri answered 25/9, 2023 at 11:57 Comment(4)
"The user can just make these themselves." Wouldn't it be desirable to have a new C++ programmer to be able to use concepts as an abstraction layer, without having to understand the underlying (and less intuitive) C++11 metaprogramming library? The other points make a lot of sense.Mossberg
@Mossberg yeah, sure. There is benefit in having additional convenience concepts, even if the implementation is relatively simple. That's why I've said that a proposal for convenience concepts could likely find success. It was just too much effort with too few returns to standardize it back in the day.Jerri
One could also imagine an external (header-only) library providing convenience concepts; not everything needs to be part of the Standard library.Raybourne
"It can be trivially constructed from std::is_arithmetic, or from std::integral and std::floating_point." This is a very poor argument as the same thing could be said about std::signed_intregral and std::unsigned_integral, which actually are provided by the standard library. You should just collapse the two arguments into a single one, where the emphasis is put on the unclarity of which construction is the preferred one.Disannul

© 2022 - 2024 — McMap. All rights reserved.