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

Libfmt v10+中如何依据<<运算符存在性条件性包装fmt::streamed()?

Libfmt 10+ 替代移除的is_streamable实现条件包装方案

问题背景

在一个包含100+重载std::ostream<<运算符类型的遗留项目中,使用libfmt的fmt/ranges.h打印vector、set等容器,原有日志打印函数logPrint<T>通过fmt::print输出参数。此前依赖fmt::detail::is_streamable模板检测类型是否重载<<运算符,存在则用fmt::streamed(arg)传入fmt::print,否则直接传入arg。升级至libfmt 10.0后,is_streamable被移除,需要找到标准替代方案,同时保持枚举、vector等类型的打印效果与libfmt v9一致。

示例代码:

#include <iostream>
#include <vector>
#include <fmt/ostream.h>
#include <fmt/ranges.h>

struct Streamable {
};

enum NonStreamableEnum {
    value
};

std::ostream& operator<<(std::ostream &os,
                         const Streamable&)
{
    return os << "Streamable";
}


int main()
{
    fmt::print("watch1: {}\n", fmt::streamed(Streamable{}));             // ok

    //fmt::print("watch2: {}\n", NonStreamableEnum::value);                // fails
    fmt::print("watch2: {}\n", fmt::streamed(NonStreamableEnum::value)); // ok

    fmt::print("watch3: {}\n", std::vector<int>({1, 2}));                // ok

    return 0;
}

解决方案

1. 自行实现is_streamable类型 trait

由于libfmt 10+移除了内部的is_streamable,可以基于SFINAE手动实现等价的类型检测模板:

#include <type_traits>
#include <ostream>

namespace detail {
  // 检测类型T是否支持std::ostream输出
  template <typename T, typename = void>
  struct is_streamable : std::false_type {};

  template <typename T>
  struct is_streamable<T, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<const T&>())>>
      : std::true_type {};

  template <typename T>
  inline constexpr bool is_streamable_v = is_streamable<T>::value;
} // namespace detail

2. 实现自动条件包装函数

基于上述trait,编写一个wrap_for_fmt函数,自动根据类型是否可流输出选择包装方式:

#include <fmt/ostream.h>

template <typename T>
auto wrap_for_fmt(const T& arg) {
  if constexpr (detail::is_streamable_v<T>) {
    return fmt::streamed(arg);
  } else {
    return arg;
  }
}

3. 适配枚举类型的特殊处理

为了保持枚举类型和libfmt v9一致的打印行为(避免直接打印时的编译错误),可以给枚举类型单独做trait特化:

// 让所有枚举类型默认使用fmt::streamed包装
template <typename T>
struct detail::is_streamable<T, std::enable_if_t<std::is_enum_v<T>>> : std::true_type {};

4. 改造原有logPrint函数

将原有logPrint<T>的参数传入逻辑替换为调用wrap_for_fmt:

template <typename T>
void logPrint(const T& arg) {
  fmt::print("{}", wrap_for_fmt(arg));
}

改造后效果:

  • 重载了std::ostream<<的类型会被fmt::streamed包装,保持原有输出格式
  • std::vector等容器类型因libfmt自带fmt/ranges.h的格式化支持,不会被包装,输出效果与v9一致
  • 枚举类型被自动包装,避免编译错误,输出行为与示例一致

内容的提问来源于stack exchange,提问作者cheater

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:00:05