std::invocable 和 std::regular_invocable 概念有什么区别?
What is the difference between std::invocable and std::regular_invocable concepts?
std::invocable
和 std::regular_invocable
有什么区别?根据来自的描述
https://en.cppreference.com/w/cpp/concepts/invocable 我希望 std::regular_invocable
概念不允许在函数对象被调用时改变它的状态(或者至少调用的结果应该总是 return同样的结果)。
为什么下面的代码可以编译?
使用命令编译: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!
}
程序输出:
1
1
1
2
Notes
The distinction between invocable and regular_invocable is purely semantic.
这意味着编译器无法通过概念系统强制区分,因为它只能检查句法属性。
从介绍到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.
假设我们可以这样写:
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
};
然而,这实际上并不会测试断言是否成立,因为 requires
子句不会在 运行 时被调用,而只会在编译时被检查。
regular_invocable
告诉函数的用户它将假定,调用具有相同参数值的 regular_invocable
函数的结果将产生相同的 return 值,并可能因此缓存该结果。
缓存结果可以由期望 regular_invocable
的函数完成,或者编译器可以使用该信息在参数值保持不变时优化对 regular_invocable
函数的多次函数调用相同。所以现在它可以被视为文档和编译器提示。
类似于const_cast
,编译器可能并不总是能够检查它是否有效。由于这个原因,并且因为目前标准中没有 attribute/keyword 来将函数标记为始终 return 相同的值,所以现在没有办法在编译时强制函数传递 regular_invocable
确实符合该要求。
std::invocable
和 std::regular_invocable
有什么区别?根据来自的描述
https://en.cppreference.com/w/cpp/concepts/invocable 我希望 std::regular_invocable
概念不允许在函数对象被调用时改变它的状态(或者至少调用的结果应该总是 return同样的结果)。
为什么下面的代码可以编译?
使用命令编译: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!
}
程序输出:
1
1
1
2
Notes
The distinction between invocable and regular_invocable is purely semantic.
这意味着编译器无法通过概念系统强制区分,因为它只能检查句法属性。
从介绍到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.
假设我们可以这样写:
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
};
然而,这实际上并不会测试断言是否成立,因为 requires
子句不会在 运行 时被调用,而只会在编译时被检查。
regular_invocable
告诉函数的用户它将假定,调用具有相同参数值的 regular_invocable
函数的结果将产生相同的 return 值,并可能因此缓存该结果。
缓存结果可以由期望 regular_invocable
的函数完成,或者编译器可以使用该信息在参数值保持不变时优化对 regular_invocable
函数的多次函数调用相同。所以现在它可以被视为文档和编译器提示。
类似于const_cast
,编译器可能并不总是能够检查它是否有效。由于这个原因,并且因为目前标准中没有 attribute/keyword 来将函数标记为始终 return 相同的值,所以现在没有办法在编译时强制函数传递 regular_invocable
确实符合该要求。