GCC结合Undefined Sanitizer与std::format时编译错误问题咨询
GCC启用Undefined Sanitizer时std::apply与std::format编译错误分析与解决
问题描述
代码在GCC或Clang下可正常编译,但仅在GCC启用Undefined Sanitizer(UBSan)时出现编译错误:std::apply被错误置于consteval上下文执行,而std::format无法在编译时求值。已测试GCC 13和14版本(附GCC 14.1.0错误信息),需明确:
- 代码本身是否存在问题?
- 代码是否应当正常编译?
- GCC是否存在处理错误?
- 如何解决该问题?
代码示例
#include <format> #include <iostream> #include <string> #include <tuple> #include <utility> template <char const *FS, typename... Args> struct Formatter { static constexpr auto formatString{FS}; std::tuple<Args...> data; Formatter(Args...args) : data{std::move(args)...} {} std::string format() const { return std::apply([](const auto &...args) -> std::string { return std::format(formatString, args...); }, data); } }; int main() { static constexpr char fs[] = "{} {} {}"; Formatter<fs, std::string, std::string, int> fmt("A", "B", 100); std::cout << fmt.format(); return 0; }
GCC 14.1.0编译错误信息
<source>: In member function 'std::string Formatter<FS, Args>::format() const [with const char* FS = (& fs); Args = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int}; std::string = std::__cxx11::basic_string<char>]': <source>:17:22: error: call to consteval function 'std::apply(_Fn&&, _Tuple&&) [with _Fn = Formatter<(& main()::fs), __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>::format() const::<lambda(const auto:38& ...)>; _Tuple = const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&]((*(UBSAN_NULL((&((const Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*)this)->Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::data), 2, 8), (&((const Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*)this)->Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::data))))' is not a constant expression 17 | return std::apply([](const auto &...args) -> std::string | ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | { return std::format(formatString, args...); }, data); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/refwrap.h:38, from /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/string:52, from /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/locale_classes.h:40, from /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/locale:41, from /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/format:47, from <source>:1: <source>:17:22: in 'constexpr' expansion of 'std::apply(_Fn&&, _Tuple&&) [with _Fn = Formatter<(& main()::fs), __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>::format() const::<lambda(const auto:38& ...)>; _Tuple = const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&]((*(UBSAN_NULL((&((const Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*)this)->Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::data), 2, 8), (&((const Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*)this)->Formatter<(& fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::data))))' /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/tuple:2936:31: in 'constexpr' expansion of 'std::__apply_impl(_Fn&&, _Tuple&&, index_sequence<_Idx ...>) [with _Fn = Formatter<(& main()::fs), __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>::format() const::<lambda(const auto:38& ...)>; _Tuple = const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&; long unsigned int ..._Idx = {0, 1, 2}; index_sequence<_Idx ...> = integer_sequence<long unsigned int, 0, 1, 2>]((* & std::forward<const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&>(* & __t))), (_Indices(), _Indices()))' /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/tuple:2921:27: in 'constexpr' expansion of 'std::__invoke(_Callable&&, _Args&& ...) [with _Callable = Formatter<(& main()::fs), __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>::format() const::<lambda(const auto:38& ...)>; _Args = {const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const int&}; typename __invoke_result<_Functor, _ArgTypes>::type = __cxx11::basic_string<char>]((* & std::get<0, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>(* & std::forward<const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&>(* & __t)))), (* & std::get<1, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>(* & std::forward<const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&>(* & __t)))), (* & std::get<2, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>(* & std::forward<const tuple<__cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>&>(* & __t))))))' /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/invoke.h:96:40: in 'constexpr' expansion of 'std::__invoke_impl(__invoke_other, _Fn&&, _Args&& ...) [with _Res = __cxx11::basic_string<char>; _Fn = Formatter<(& main()::fs), __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>::format() const::<lambda(const auto:38& ...)>; _Args = {const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const int&}]((* & std::forward<Formatter<(& main()::fs), __cxx11::basic_string<char, char_traits<char>, allocator<char> >, __cxx11::basic_string<char, char_traits<char>, allocator<char> >, int>::format() const::<lambda(const auto:38& ...)> >(* & __fn))), (* & std::forward<const __cxx11::basic_string<char>&>(* & __args#0))), (* & std::forward<const __cxx11::basic_string<char>&>(* & __args#1))), (* & std::forward<const int&>(* & __args#2))))' /opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/bits/invoke.h:61:36: error: 'Formatter<(& main()::fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::format() const::<lambda(const auto:38& ...)> [with auto:38 = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int}; std::string = std::__cxx11::basic_string<char>]' called in a constant expression 61 | { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); } | ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:17:23: note: 'Formatter<(& main()::fs), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>::format() const::<lambda(const auto:38& ...)> [with auto:38 = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int}; std::string = std::__cxx11::basic_string<char>]' is not usable as a 'constexpr' function because: 17 | return std::apply([](const auto &...args) -> std::string | ^ <source>:18:55: error: call to non-'constexpr' function 'std::string std::format(format_string<_Args ...>, _Args&& ...) [with _Args = {const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const int&}; string = __cxx11::basic_string<char>; format_string<_Args ...> = basic_format_string<char, const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const __cxx11::basic_string<char, char_traits<char>, allocator<char> >&, const int&>]' 18 | { return std::format(formatString, args...); }, data);
问题分析与解答
1. 代码本身是否存在问题?
代码逻辑无错误,std::apply转发tuple参数给std::format的写法是合法的运行时调用,不存在语法或语义问题。
2. 代码是否应当正常编译?
是的,代码应当正常编译。std::format是常规运行时函数(C++20中仅特定场景支持constexpr调用,此处不满足),而std::apply的调用上下文是运行时,编译器不应强制将其放入consteval上下文求值。
3. GCC是否存在处理错误?
这是GCC的已知bug:启用UBSan时,编译器错误地将std::apply的调用上下文标记为consteval,导致尝试编译时求值不支持constexpr的std::format,进而触发错误。该问题存在于GCC 13、14版本,后续版本可能修复。
4. 解决方案
方案1:打破lambda的constexpr推导链
将std::format的结果先存入局部变量再返回,阻止编译器将lambda视为constexpr函数:
std::string format() const { return std::apply([](const auto &...args) -> std::string { auto res = std::format(formatString, args...); return res; }, data); }
方案2:手动展开tuple(参数数量固定时)
如果tuple的参数数量固定,直接手动展开替代std::apply:
std::string format() const { return std::format(formatString, std::get<0>(data), std::get<1>(data), std::get<2>(data)); }
方案3:升级GCC版本
等待GCC官方修复该UBSan相关bug,升级到修复后的版本即可解决。
内容的提问来源于stack exchange,提问作者rnorthcott
相关产品推荐
相关产品推荐

