What is the difference between std::invocable
and std::regular_invocable
? Based on the description from
https://en.cppreference.com/w/cpp/concepts/invocable I would expect that the std::regular_invocable
concept doesn't allow to change the state of of the function object when it is called (or at least the result of the calling should always return the same result).
Why the code below compiles?
Compiled with a command: g++-10 -std=c++2a ./main.cc
.
#include <iostream>
#include <concepts>
using namespace std;
template<std::regular_invocable F>
auto call_with_regular_invocable_constraint(F& f){
return f();
}
template<std::invocable F>
auto call_with_invocable_constraint(F& f){
return f();
}
class adds_one {
int state{0};
public:
int operator()() {
state++;
return state;
}
};
int main()
{
auto immutable_function_object([]() { return 1; });
adds_one mutable_function_object;
// I would expect only first three will be compiled and the last one will fail to compile because the procedure is
// not regular (that is does not result in equal outputs given equal inputs).
cout << call_with_invocable_constraint(immutable_function_object) << endl;
cout << call_with_invocable_constraint(mutable_function_object) << endl;
cout << call_with_regular_invocable_constraint(immutable_function_object) << endl;
cout << call_with_regular_invocable_constraint(mutable_function_object) << endl; // Compiles!
}
Output of the program:
1
1
1
2
From the reference:
Notes
The distinction between invocable and regular_invocable is purely semantic.
This means that there is no way for the compiler to enforce the distinction through the concepts system, since that can only check syntactic properties.
From the introduction to the concepts library:
In general, only the syntactic requirements can be checked by the compiler. If the validity or meaning of a program depends whether a sequenced of template arguments models a concept, and the concept is satisfied but not modeled, or if a semantic requirement is not met at the point of use, the program is ill-formed, no diagnostic required.
Hypothetically, we could write:
template< class F, class... Args >
concept regular_invocable = invocable<F, Args...> &&
requires(F&& f, Args&&... args) {
auto prev = f;
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
assert(f == prev);
// TODO assert that `args` are unchanged
// TODO assert that invoking `f` a second time gives the same result
};
However, this would not actually test that the assertion holds, since a requires
clause is not invoked at run time but only checked at compile time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With