Symptom
Building seastar with -std=c++26 on clang-20+/gcc-16 against libstdc++-16 (Ubuntu 26.04) fails with the cooked fmt 11.2.0 (and also with the recently released fmt 12.0.0 / 12.1.0). Affected TU in our matrix is tests/unit/sstring_test.cc:
fmt/base.h:2262:45: error: implicit instantiation of undefined template
'fmt::detail::type_is_unformattable_for<std::optional<seastar::basic_sstring<char, unsigned int, 15>>, char>'
sstring_test.cc:322:
std::ignore = fmt::format("{}", std::optional(sstring{"hello"}));
A minimal local probe shows the real issue is ambiguous partial specializations:
error: ambiguous partial specializations of
'formatter<std::optional<seastar::basic_sstring<char, unsigned int, 15>>>'
fmt/ranges.h:491: partial specialization matches [with R = std::optional<...>, Char = char]
fmt/std.h:217: partial specialization matches [with T = ..., Char = char]
is_formattable<std::optional<sstring>> therefore evaluates false, which is what surfaces as the cryptic type_is_unformattable_for error at the call site.
Root cause
libstdc++-16 implements P3168R2 (std::optional range support) for C++26, so std::optional<T> now models std::ranges::range. fmt 11.2.0 / 12.0.0 / 12.1.0 have two unconstrained formatter partial specializations that both match std::optional<T> once it's a range:
fmt/std.h — formatter for std::optional<T> gated only on is_formattable<T> (correct).
fmt/ranges.h — generic range formatter that matches any type whose range_format_kind is non-disabled, including the new optional-as-range.
This is fmtlib/fmt#4760, fixed by fmtlib/fmt#4761 (merged 2026-05-03, master commit 9cb8c0f92b4c345fb974a75d71370c23047528aa). No tagged fmt release carries the fix yet (12.1.0 was 2025-10-29).
Reproduction
Run inside an ubuntu:26.04 container:
apt-get update
apt-get install -y --no-install-recommends g++ libfmt-dev clang-22 libstdc++-16-dev
cat > /tmp/t.cc <<EOF
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <fmt/std.h>
#include <optional>
#include <string>
int main() {
using F = fmt::formatter<std::optional<std::string>>;
F f;
(void)f;
}
EOF
clang++-22 -std=gnu++26 /tmp/t.cc -c -o /tmp/t.o
Substitute seastar::basic_sstring for std::string to see the in-tree variant.
Workaround in our CI branch
A new fmt-12-1-dev cooking ingredient is added in cooking_recipe.cmake, pinned to the post-12.1.0 fix commit. C++26 matrix entries pass --cook fmt-12-1-dev instead of the default --cook fmt (which still pulls 11.2.0).
Tracking goal
When fmt ships a tagged release containing fmtlib/fmt#4761, bump the default cooking_ingredient (fmt ...) URL in cooking_recipe.cmake to that tag and delete fmt-12-1-dev.
Symptom
Building seastar with
-std=c++26on clang-20+/gcc-16 against libstdc++-16 (Ubuntu 26.04) fails with the cooked fmt 11.2.0 (and also with the recently released fmt 12.0.0 / 12.1.0). Affected TU in our matrix istests/unit/sstring_test.cc:A minimal local probe shows the real issue is ambiguous partial specializations:
is_formattable<std::optional<sstring>>therefore evaluatesfalse, which is what surfaces as the cryptictype_is_unformattable_forerror at the call site.Root cause
libstdc++-16 implements P3168R2 (
std::optionalrange support) for C++26, sostd::optional<T>now modelsstd::ranges::range. fmt 11.2.0 / 12.0.0 / 12.1.0 have two unconstrained formatter partial specializations that both matchstd::optional<T>once it's a range:fmt/std.h— formatter forstd::optional<T>gated only onis_formattable<T>(correct).fmt/ranges.h— generic range formatter that matches any type whoserange_format_kindis non-disabled, including the new optional-as-range.This is fmtlib/fmt#4760, fixed by fmtlib/fmt#4761 (merged 2026-05-03, master commit
9cb8c0f92b4c345fb974a75d71370c23047528aa). No tagged fmt release carries the fix yet (12.1.0 was 2025-10-29).Reproduction
Run inside an
ubuntu:26.04container:Substitute
seastar::basic_sstringforstd::stringto see the in-tree variant.Workaround in our CI branch
A new
fmt-12-1-devcooking ingredient is added incooking_recipe.cmake, pinned to the post-12.1.0 fix commit. C++26 matrix entries pass--cook fmt-12-1-devinstead of the default--cook fmt(which still pulls 11.2.0).Tracking goal
When fmt ships a tagged release containing fmtlib/fmt#4761, bump the default
cooking_ingredient (fmt ...)URL incooking_recipe.cmaketo that tag and deletefmt-12-1-dev.