为什么可以在函数内的常量表达式中使用作为 constexpr 函数参数的参数传递的 lambda 的返回值?

Oer*_*ted 9 c++ constant-expression callable-object constexpr-function

问题标题的措辞可能不正确,我会很乐意根据您的建议进行修复。

我的问题可以用这个片段来说明:

#include <array>

template <typename Callable>
constexpr auto make_array_ok(Callable callable) {
    return std::array<int, callable()>{};
};
// constexpr auto make_array_bad(std::size_t s)
// {
//     return std::array<int,s>{};
// };

int main(int argc, char**) {
    static_cast<void>(argc);

    auto size = []() { return std::size_t{42}; };

    // fails to compile -- as I expected
    // auto size_dyn = [argc]() { return std::size_t(argc); };
    // auto a = make_array_ok(size_dyn);

    // also fails to compile -- but why?
    // auto size_capt = [arg = size()]()constexpr{return arg;};
    // auto a = make_array_ok(size_capt);

    auto a = make_array_ok(size);
    return a.size();
}
Run Code Online (Sandbox Code Playgroud)

居住

在需要常量表达式的情况下,constexpr 函数参数被认为不可用(因为 constexpr 函数可以在运行时使用运行时参数调用),因此make_array_bad编译失败是很常见的。但是在什么规则下可以make_array_ok编译(当与返回 lambda 一起使用时42)?

我怀疑这是因为operator()从 C++17 开始默认 lambda 是 constexpr,但我在 cppreference 中没有找到有关 constexpr 函数的 有关其参数在常量表达式中的可用性的详细信息。

但为什么它不适用于该size_capt版本呢?

Enr*_*lis 8

(这是对原始问题的回答。)

该代码起作用的原因在注释中,但分享可以遵循的思维过程以获得答案可能很有用:

  • 模板参数必须在编译时已知,
  • 因此,如果std::array<int, callable()>编译,callable()必须在编译时知道,
  • 这意味着它callable是一个constexpr可调用的(例如constepxr函数或constexpr operator()lambda 的);
  • 既然你知道你正在传递 lambda 作为callable,问题就变成了:为什么是operator() constexprfor[]() { return std::size_t{42}; }而不是 for [argc]() { return std::size_t(argc); }?答案是另一个答案:D

关于更新的问题,我认为这一切都归结为一个更简单的例子:为什么以下内容无法编译?

#include <array>

constexpr auto lambda = [arg = 0]() constexpr { return arg; };

constexpr auto make_array(decltype(lambda) callable) {
    return std::array<int, callable()>{};
};
Run Code Online (Sandbox Code Playgroud)

where0绝对是一个常量表达式,就像 一样size(),但会减少怀疑的空间。

关键在于函数参数不是constexpr,从某种意义上说,跨函数边界它们会失去constexpr本质,因此即使是lambda 的状态,它是对象的一部分,而不是其类型的一部分(就像0Foo{}定的一样struct Foo { int i{0}; };,但是与嵌入在类型本身中的a 的大小不同std::array,不能在常量表达式中使用。


use*_*570 5

第一个 lambda 是隐式 constexpr,而第二个 lambda 不是。这可以从lambda 表达式的文档中看到:

说明符 影响
constexpr (C++17 起) 显式指定这operator()是一个 constexpr 函数。如果operator()满足所有 constexpr 函数要求,即使 constexpr 不存在也会如此operator()constexpr

由于operator()for 第一个 lambda 满足constexpr 函数的所有要求,因此它operator()隐式是 constexpr。