You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:39:50