【问题标题】:What is the difference between std::invocable and std::regular_invocable concepts?std::invocable 和 std::regular_invocable 概念有什么区别?
【发布时间】:2021-02-20 15:25:05
【问题描述】:

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

【问题讨论】:

  • auto mutable_fun = [n=0] () mutable { return n++; }; 这也适用于regular_invocable
  • @pptaszni 它确实有效,但语义不正确。

标签: c++ generic-programming c++20


【解决方案1】:

来自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 概念不能保证F 是常规的?这有点令人困惑,因为在他们写“regular_invocable 概念通过要求调用表达式保持相等性而增加了可调用概念”之前,
  • 对于您的示例,f 不是函数,assert(f == prev); 检查函数是否保持不变,而不是检查它是否会返回相同的值?
  • @pptaszni 是的,它需要它但不保证它。这有点像能够将[[attribute(pure)]] 放在函数声明/定义上。也许将来编译器将能够帮助检查这些要求,但现在需要程序员来确保它正确。
  • @t.niese 是的,我还需要检查函数参数是否未更改 - 我会说明断言不完整
【解决方案2】:

regular_invocable 告诉函数的用户它将假定,调用具有相同参数值的regular_invocable 函数的结果将产生相同的返回值,并且可能因此缓存该结果。

缓存结果可以由期望regular_invocable 的函数完成,或者编译器可以使用该信息在参数值保持不变时优化​​对regular_invocable 函数的多个函数调用。所以现在它可以被看作是文档和编译器提示。

类似于const_cast,编译器可能并不总是可以检查它是否有效。由于这个原因,并且由于标准中目前没有属性/关键字来标记函数始终返回相同的值,所以现在无法在编译时强制通过 regular_invocable 的函数确实符合该要求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 2023-04-09
    • 2020-10-16
    • 2020-09-30
    • 2021-10-16
    • 2015-07-18
    • 2019-02-26
    相关资源
    最近更新 更多