small_vector::view的std::formatter特化编译失败的原因及解决方法
我实现了一个模板类small_vector<typename T, size_t N>,其中T是值类型,N是内联桶大小,类内部定义了嵌套的view类:
template <typename T, size_t N> class small_vector { // ... 其他成员 class view { // ... view的成员 }; };
为了给view类定制std::format格式化器,我写了以下代码:
template<typename T, size_t N> using small_vector_view = typename small_vector<T, N>::view; template<typename T, size_t N> struct std::formatter<typename small_vector<T, N>::view, char> { enum class Style { Default, Compact, Pretty }; Style style = Style::Default; constexpr auto parse(format_parse_context &ctx) { auto it = ctx.begin(); if (it == ctx.end() || *it == '}') return it; switch (*it) { case 'c': style = Style::Compact; break; case 'p': style = Style::Pretty; break; default: throw format_error("invalid format specifier for small_vector"); } return ++it; } auto format(const typename small_vector<T, N>::view &v, format_context &ctx) const { if (v.empty()) { if (style == Style::Pretty) { return format_to(ctx.out(), "[\n]"); } return format_to(ctx.out(), "[]"); } std::string result; if (style == Style::Pretty) { result = "[\n "; } else { result = "["; } for (size_t i = 0; i < v.size(); ++i) { if (i > 0) { if (style == Style::Pretty) { result += ",\n "; } else if (style == Style::Compact) { result += ","; } else { result += ", "; } } if constexpr (std::is_same_v<T, std::string>) { result += std::format("\"{}\"", v[i]); } else { result += std::format("{}", v[i]); } } if (style == Style::Pretty) { result += "\n]"; } else { result += "]"; } return format_to(ctx.out(), "{}", result); } };
但代码无法编译,MSVC编译器报错:
D:\repos\ustring\small_vector.h(602): error C2764: 'T': template parameter not used or deducible in partial specialization 'std::formatter<small_vector<T,InlineCapacity>::view,char>' D:\repos\ustring\small_vector.h(602): error C2764: 'N': template parameter not used or deducible in partial specialization 'std::formatter<small_vector<T,InlineCapacity>::view,char>' D:\repos\ustring\small_vector_tests.cpp(557): error C7595: 'std::basic_format_string<char,small_vector<int,16>::view &>::basic_format_string': call to immediate function is not a constant expression
我想知道为什么编译器无法推导T和N,以及如何修复代码使其编译通过。
模板参数推导失败:
在偏特化std::formatter<typename small_vector<T, N>::view, char>中,typename small_vector<T, N>::view属于非推导上下文——编译器无法从view类型反向推导出外层small_vector的模板参数T和N。C++标准规定,嵌套依赖类型不能作为模板参数推导的依据。常量表达式违反:
C7595错误是因为parse函数中直接抛出format_error,而std::basic_format_string的构造是即时函数,要求parse在常量表达式上下文中不能抛出异常,必须使用格式化上下文提供的错误处理机制而非直接抛出异常。
步骤1:给view类添加嵌套类型与常量,暴露T和N
修改small_vector的view定义,添加嵌套类型和静态常量,让外部能获取到对应的T和N:
template <typename T, size_t N> class small_vector { public: class view { public: // 暴露外层模板参数 using value_type = T; static constexpr size_t inline_capacity = N; // ... 原view的其他成员 }; // ... 原small_vector的其他成员 };
步骤2:使用Concept约束实现Formatter偏特化
定义一个Concept来识别small_vector::view类型,避免直接依赖外层模板参数推导:
#include <concepts> template <typename View> concept SmallVectorView = requires(View) { typename View::value_type; requires std::integral_constant<size_t, View::inline_capacity>::value; // 验证该View确实对应small_vector的view类型 requires std::same_as<View, typename small_vector<typename View::value_type, View::inline_capacity>::view>; };
然后基于这个Concept实现std::formatter的偏特化:
template <SmallVectorView View> struct std::formatter<View, char> { using T = typename View::value_type; static constexpr size_t N = View::inline_capacity; enum class Style { Default, Compact, Pretty }; Style style = Style::Default; constexpr auto parse(format_parse_context &ctx) { auto it = ctx.begin(); if (it == ctx.end() || *it == '}') return it; switch (*it) { case 'c': style = Style::Compact; break; case 'p': style = Style::Pretty; break; default: // 使用ctx.on_error替代直接抛出异常,满足常量表达式要求 ctx.on_error("invalid format specifier for small_vector view"); return it; } return ++it; } auto format(const View &v, format_context &ctx) const { if (v.empty()) { if (style == Style::Pretty) { return format_to(ctx.out(), "[\n]"); } return format_to(ctx.out(), "[]"); } std::string result; if (style == Style::Pretty) { result = "[\n "; } else { result = "["; } for (size_t i = 0; i < v.size(); ++i) { if (i > 0) { if (style == Style::Pretty) { result += ",\n "; } else if (style == Style::Compact) { result += ","; } else { result += ", "; } } if constexpr (std::is_same_v<T, std::string>) { result += std::format("\"{}\"", v[i]); } else { result += std::format("{}", v[i]); } } if (style == Style::Pretty) { result += "\n]"; } else { result += "]"; } return format_to(ctx.out(), "{}", result); } };
步骤3:优化format函数(可选)
原format函数中先构建std::string再输出可以优化为直接写入ctx.out(),减少内存分配:
auto format(const View &v, format_context &ctx) const { auto out = ctx.out(); if (v.empty()) { if (style == Style::Pretty) { return format_to(out, "[\n]"); } return format_to(out, "[]"); } if (style == Style::Pretty) { out = format_to(out, "[\n "); } else { out = format_to(out, "["); } for (size_t i = 0; i < v.size(); ++i) { if (i > 0) { if (style == Style::Pretty) { out = format_to(out, ",\n "); } else if (style == Style::Compact) { out = format_to(out, ","); } else { out = format_to(out, ", "); } } if constexpr (std::is_same_v<T, std::string>) { out = format_to(out, "\"{}\"", v[i]); } else { out = format_to(out, "{}", v[i]); } } if (style == Style::Pretty) { out = format_to(out, "\n]"); } else { out = format_to(out, "]"); } return out; }
内容的提问来源于stack exchange,提问作者Nekomiya Kasane

