如何在C++17中检查无参Lambda是否可被constexpr求值?
在C++17中检查无参Lambda是否可常量求值的问题
现有方案的缺陷
我曾找到Stack Overflow上的一个实现方案:
template <class Lambda, int = (Lambda{}(), 0)> constexpr std::true_type is_cx_invocable(Lambda) { return {}; } constexpr std::false_type is_cx_invocable(...) { return {}; }
但它存在两个明显问题:
- 仅支持C20:依赖无捕获Lambda可默认构造的特性,不符合C17的需求
- 结果歧义:返回
false时,无法区分是Lambda不可默认构造,还是其调用运算符非constexpr
比如以下示例会出现错误判断:
int main() { static_assert(is_cx_invocable([] { return 0; })); int i{}; auto cxLambda = [i] { return 0; }; (std::array<int, cxLambda()>{}, (void)0); // ^ cxLambda确实可常量求值 // (但不可默认构造) auto isCxLambdaCx = is_cx_invocable(cxLambda); static_assert(!isCxLambdaCx); // 此处断言失败 }
我尝试的替代方案
为解决上述问题,我实现了另一个版本:
template <class Lambda> constexpr auto is_cx_invocable(Lambda lambda) -> std::integral_constant<bool, (lambda(), 1)> { // 或使用enable_if_t版本 //-> std::enable_if_t<(lambda(), 1), std::true_type> { return {}; } constexpr std::false_type is_cx_invocable(...) { return {}; }
这个方案的优势:
- 不依赖C++20特性
- 能正确判断带捕获但可常量求值的Lambda,比如下面的示例可以通过断言:
int main() { int i{}; auto cxLambda = [i] { return 0; }; auto isCxLambdaCx = is_cx_invocable(cxLambda); static_assert(isCxLambdaCx); // 现在断言通过 }
但该代码在Clang和MSVC中可正常编译,却无法通过GCC编译。
我有两个疑问:
- 我的方案是否属于非法代码(即GCC拒绝它是符合标准的)?
- 是否存在符合ISO C标准、可移植的方法,在C17中实现
is_cx_invocable检查?
解答
1. 你的方案是否非法?
你的代码符合C++17标准,GCC的拒绝属于编译器实现bug。
在C17中,constexpr函数的参数若为常量表达式对象(比如示例中带常量初始化捕获的Lambda),允许在常量表达式中使用。你的方案通过返回类型推导中的lambda()触发SFINAE:当lambda()无法在常量求值环境中执行时,模板重载会被丢弃,转而匹配...版本的重载。这完全符合C17的SFINAE规则,GCC在此处的行为不符合标准。
2. 可移植的C++17实现方案
我们可以利用C++17的SFINAE特性,通过在constexpr上下文内尝试调用Lambda来实现可移植的检查:
#include <type_traits> namespace detail { // 尝试在constexpr上下文调用Lambda,仅当调用合法时匹配此重载 template <class Lambda> constexpr auto test(Lambda lambda) -> decltype( []() constexpr { constexpr auto dummy = lambda(); (void)dummy; return std::true_type{}; }() ) { return std::true_type{}; } // 匹配所有无法常量调用的情况 constexpr std::false_type test(...) { return std::false_type{}; } } template <class Lambda> constexpr auto is_cx_invocable(Lambda lambda) { return detail::test(std::move(lambda)); }
这个实现通过嵌套的constexpr Lambda强制在常量求值环境中尝试调用传入的Lambda,只有当调用合法时,外层模板重载才会被选中,否则会匹配...版本的重载返回false_type。该代码在Clang、MSVC和GCC中均可正常编译运行,完全符合C++17标准。
内容的提问来源于stack exchange,提问作者paolo
相关产品推荐
相关产品推荐

