std :: invocable和std :: regular_invocable概念之间有什么区别? 注释

问题描述

std::invocablestd::regular_invocable有什么区别?根据来自的描述 https://en.cppreference.com/w/cpp/concepts/invocable我希望std::regular_invocable概念不允许在调用函数对象时更改其状态(或者至少调用的结果应始终返回相同的结果)。

为什么下面的代码可以编译?

使用命令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

解决方法

来自the reference

注释

invocable和regular_invocable之间的区别纯粹是语义上的。

这意味着编译器无法通过概念系统强制执行区分,因为这只能检查语法属性。

从简介到concepts library

通常,编译器只能检查语法要求。如果程序的有效性或含义取决于模板参数序列是否为一个概念建模,并且满足但未建模该概念,或者在使用时不满足语义要求,则该程序格式错误,否需要诊断。

假设地,我们可以这样写:

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函数的结果将产生相同的返回值,并可能缓存该结果因此。

缓存结果可以由期望regular_invocable的函数完成,或者当参数的值保持相同时,编译器可以使用该信息来优化对regular_invocable函数的多个函数调用。因此,现在可以将其视为文档和编译器提示。

类似于const_cast,编译器可能并不总是能够检查其是否有效。因此,由于标准中目前没有属性/关键字来标记函数始终返回相同的值,因此目前尚无办法在编译时强制传递的regular_invocable函数确实符合该要求