为何constexpr函数中可使用lambda返回值作为常量表达式?
代码示例
#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>{}; // }; // 直接返回参数的constexpr函数 template <std::size_t S> constexpr std::size_t foo() { return S; } int main(int argc, char**) { static_cast<void>(argc); auto size = []() { return std::size_t{42}; }; // 编译失败——符合预期 // auto size_dyn = [argc]() { return std::size_t(argc); }; // [[maybe_unused]] auto a = make_array_ok(size_dyn); // 同样编译失败——原因何在? [[maybe_unused]] auto size_capt = [arg = size()]() { return arg; }; // [[maybe_unused]] auto b = make_array_ok(size_capt); // 同样编译失败——原因何在? [[maybe_unused]] constexpr auto size_capt_ce = [arg = size()]() { return arg; }; // [[maybe_unused]] auto c = make_array_ok(size_capt_ce); // 直接调用也编译失败——是否因闭包非constexpr?当闭包为constexpr时直接调用可编译 // [[maybe_unused]] auto d = std::array<int,size_capt()>{}, [[maybe_unused]] auto e = std::array<int, size_capt_ce()>{}, // 用constexpr函数替代lambda的简单示例 [[maybe_unused]] auto f = std::array<int, foo<42>()>{}, // 通过函数调用constexpr函数编译失败——原因何在? // [[maybe_unused]] auto g = make_array_ok(foo<42>); // 无捕获lambda可正常编译 [[maybe_unused]] auto h = make_array_ok(size); return size_capt_ce(); }
核心疑问与解析
1. 无捕获lambda可编译,普通参数却不行的原因
无捕获lambda的operator()在C++17及以后默认是constexpr,且其行为完全编译期可知,没有运行时依赖。当传入模板函数make_array_ok时,编译器在模板实例化阶段能确定callable()的结果是编译期常量。
而make_array_bad的参数是std::size_t s,即使函数是constexpr,形参s本身不属于常量表达式的允许范畴——constexpr函数可被运行时调用,编译器无法保证s一定是编译期常量,因此不能用于std::array的模板参数。
2. 带捕获lambda传入make_array_ok编译失败的原因
带捕获lambda本质是带有成员变量的闭包类,当闭包对象作为形参传入make_array_ok时,callable()的调用依赖于这个形参对象。在常量表达式上下文里,函数形参(即使传入的是constexpr对象)的成员访问或方法调用是不被允许的——编译器无法将形参视为“常量表达式中的核心实体”,因此无法用其结果作为std::array的大小。
而直接调用size_capt_ce()时,size_capt_ce是constexpr变量,其方法调用直接在编译期计算,不经过函数形参传递,因此符合常量表达式要求。
3. 模板函数foo作为参数编译失败的原因
把foo<42>传给make_array_ok时,callable是函数指针形参。在常量表达式中,函数形参本身的不确定性(理论上可传入其他函数指针)导致编译器无法保证callable()的结果是编译期常量,因此无法用于模板参数。而直接调用foo<42>()时,编译器能直接确定结果为编译期常量,所以合法。
关键规则总结
在C++常量表达式上下文里:
- 函数形参(除非是C++20的
constexpr形参)不能用于生成编译期常量。 - 无捕获lambda因行为完全编译期可知,其调用在模板实例化时可被确定为常量,因此能通过编译。
- 带捕获闭包对象、函数指针作为形参时,其调用依赖于形参本身,不符合常量表达式的核心要求,无法用于模板参数。
内容的提问来源于stack exchange,提问作者Oersted

