基于无参Lambda的SFINAE触发GCC编译错误,代码是否合法?
代码有效性与GCC内部错误分析
问题背景
在研究相关问题时,遇到疑似GCC的Bug,触发代码如下:
#include <type_traits> constexpr auto f = [](int){}; template<typename T> auto canCallFOn(T) -> decltype([]{ f(T{}); }(), std::true_type{}); constexpr std::false_type canCallFOn(...) { return {}; } int main() { static_assert(!canCallFOn(1)); static_assert(!canCallFOn("")); }
原本预期:当模板重载不可用时调用非模板重载,模板重载仅在[]{ f(T{}); }()在实例化上下文合法时可用。但根据编译错误,怀疑代码本身无效,因此提出疑问:
- 该代码是否确实无效?
- 若无效,原因是什么?
- 为何会触发内部编译器错误?
使用g++ -std=c++20 source.cpp编译时,报错如下:
source.cpp:13:19: error: non-constant condition for static assertion 13 | static_assert(!canCallFOn(1)); | ^~~~~~~~~~~~~~ source.cpp:13:30: error: call to non-‘constexpr’ function ‘decltype ((<lambda>(), std::true_type{})) canCallFOn(T) [with T = int; decltype ((<lambda>(), std::true_type{})) = std::integral_constant<bool, true>; std::true_type = std::integral_constant<bool, true>]’ 13 | static_assert(!canCallFOn(1)); | ~~~~~~~~~~^~~ source.cpp:6:6: note: ‘decltype ((<lambda>(), std::true_type{})) canCallFOn(T) [with T = int; decltype ((<lambda>(), std::true_type{})) = std::integral_constant<bool, true>; std::true_type = std::integral_constant<bool, true>]’ declared here 6 | auto canCallFOn(T) -> decltype([]{ f(T{}); }(), std::true_type{}); | ^~~~~~~~~~ source.cpp: In substitution of ‘template<class T> decltype ((<lambda>(), std::true_type{})) canCallFOn(T) [with T = const char*]’: source.cpp:14:30: required from here source.cpp:6:37: error: no match for call to ‘(const<lambda(int)>) (const char*)’ 6 | auto canCallFOn(T) -> decltype([]{ f(T{}); }(), std::true_type{}); | ~^~~~~ source.cpp:6:37: note: candidate: ‘void (*)(int)’ (conversion) source.cpp:6:37: note: conversion of argument 2 would be ill-formed: source.cpp:6:37: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive] 6 | auto canCallFOn(T) -> decltype([]{ f(T{}); }(), std::true_type{}); | ~^~~~~ | | | const char* source.cpp:3:20: note: candidate: ‘<lambda(int)>’ (near match) 3 | constexpr auto f = [](int){}; | ^ source.cpp:3:20: note: conversion of argument 1 would be ill-formed: source.cpp:6:37: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive] 6 | auto canCallFOn(T) -> decltype([]{ f(T{}); }(), std::true_type{}); | ~^~~~~ | | | const char* source.cpp:14:19: error: non-constant condition for static assertion 14 | static_assert(!canCallFOn("")); | ^~~~~~~~~~~~~~~ ‘ internal compiler error: error reporting routines re-entered. 0x1a02e48 error(char const*, ...) ???:0 0x68b641 build_op_call(tree_node*, vec<tree_node*, va_gc, vl_embed>**, int) ???:0 0x810f2d finish_call_expr(tree_node*, vec<tree_node*, va_gc, vl_embed>**, bool, bool, int) ???:0 0x7e1f0c tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool) ???:0 0x7f805c tsubst_lambda_expr(tree_node*, tree_node*, int, tree_node*) ???:0 0x7e213f tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool) ???:0 0x7e06a7 tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool) ???:0 0x7e09af tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool) ???:0 0x7e3fd9 tsubst(tree_node*, tree_node*, int, tree_node*) ???:0 0x19fb329 pp_format(pretty_printer*, text_info*) ???:0 0x1a00e88 diagnostic_report_diagnostic(diagnostic_context*, diagnostic_info*) ???:0 0x1a032fc error_at(unsigned int, char const*, ...) ???:0 0x6a5f1c require_rvalue_constant_expression(tree_node*) ???:0 0x7c7a9c c_parse_file() ???:0 0x8dcedd c_common_parse_file() ???:0 Please submit a full bug report, with preprocessed source (by using -freport-bug). Please include the complete backtrace with any bug report. See <https://bugs.archlinux.org/> for instructions.
代码有效性分析
1. 代码确实无效,核心原因:
- 模板重载未声明为constexpr:模板版本的
canCallFOn没有标记constexpr,而static_assert要求表达式必须是常量表达式。即便返回值是std::true_type常量,函数本身非constexpr的属性会导致调用无法在编译时求值,直接触发"non-constant condition"错误。 - lambda内部错误无法触发SFINAE:C++的SFINAE机制仅捕获模板参数替换过程中直接出现的语法/语义错误。此处
decltype内的lambda是独立闭包类型,其内部f(T{})的调用错误属于lambda实例化阶段的错误,而非模板参数替换的直接错误,因此不会触发SFINAE回退到非模板重载,反而会产生硬编译错误。
2. 内部编译器错误的原因
GCC在处理lambda内部错误的报错逻辑时出现了递归调用(错误报告例程重入),这属于编译器自身的实现缺陷。当模板替换触发lambda内部的调用错误时,编译器的错误处理机制出现异常,导致内部崩溃。
修正后的代码示例
要实现预期的检测逻辑,可直接在decltype中调用f,或使用C++20的requires表达式:
方式一:简化decltype写法
#include <type_traits> constexpr auto f = [](int){}; template<typename T> constexpr auto canCallFOn(T) -> decltype(f(T{}), std::true_type{}) { return {}; } constexpr std::false_type canCallFOn(...) { return {}; } int main() { static_assert(canCallFOn(1)); // 正确:f可接受int static_assert(!canCallFOn("")); // 正确:f无法接受const char* }
方式二:C++20 requires表达式
#include <type_traits> constexpr auto f = [](int){}; template<typename T> constexpr std::true_type canCallFOn(T) requires requires { f(T{}); } { return {}; } constexpr std::false_type canCallFOn(...) { return {}; } int main() { static_assert(canCallFOn(1)); static_assert(!canCallFOn("")); }
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

