Skip to content

Don't let ADL pick to_string_view - #4938

Merged
vitaut merged 2 commits into
fmtlib:mainfrom
tiagomacarios:qualify-to-string-view
Sep 12, 2026
Merged

Don't let ADL pick to_string_view#4938
vitaut merged 2 commits into
fmtlib:mainfrom
tiagomacarios:qualify-to-string-view

Conversation

@tiagomacarios

Copy link
Copy Markdown
Contributor

02bf4d1c ("Disable to_string_view ADL") qualified the is_string trait, and has_to_string_view and char_t are qualified for the same reason. Two call sites were missed:

  • core.hvalue's string constructor
  • format.h — the write() overload for types with a string view conversion

Both sit inside fmt::detail, so an unqualified call adds the argument's own namespace to the overload set.

That splits the two halves of one decision. Both call sites are reached only through has_to_string_view or char_t, which are defined by the qualified expression — so ADL can never be needed to satisfy them, it can only add candidates the gate never considered. When the argument's namespace declares a to_string_view template, the two tie during partial ordering and the call is ambiguous:

core.h(2211): error C2668: 'to_string_view': ambiguous call to overloaded function
note: could be 'string_view N::to_string_view<T>(const T&)' [found using argument-dependent lookup]
note: or 'basic_string_view<char> fmt::detail::to_string_view<T,0>(const T&)'

Both are reachable:

  • format("{}", x) stores the argument through value's constructor.
  • to_string(x) passes it to detail::write unmapped, which lands on the write() overload — as do FMT_COMPILE named fields and nested_formatter::write_arg.

This turned up in Microsoft Office, which declares a constrained to_string_view template next to its own string types. It only breaks where those types are distinct classes; where they are std aliases the ADL set is namespace std and picks up nothing.

Tests

base_test.adl_to_string_view in core-test and format_test.adl_to_string_view in format-test. core-test cannot include format.h (it has an #error guard), so each call site needs its own test. Reverting either line alone fails the build of the test that covers it.

Full suite green at C++17 on MSVC 19.51 — 22/22 test binaries.

One behaviour change worth flagging

A non-template to_string_view in the argument's namespace previously won outright at these call sites. A type that satisfies is_std_string_like through find_first_of and data() but has no size() therefore used to format through ADL, and now fails to compile: the trait only checks that the qualified overload is viable, not that its body instantiates. That is the extension point removed in 02bf4d1 going away rather than a new restriction.

02bf4d1 disabled ADL for to_string_view by qualifying the is_string trait,
and has_to_string_view and char_t are qualified for the same reason. Two call
sites were left behind: value's string constructor and the write() overload
for types with a string view conversion. Both sit inside fmt::detail, where an
unqualified call adds the argument's own namespace to the overload set.

That splits the two halves of one decision. Both call sites are reached only
through has_to_string_view or char_t, which are defined by the qualified
expression, so ADL can never be needed to satisfy them - it can only add
candidates the gate never considered. When the argument's namespace declares a
to_string_view template, the two tie during partial ordering and the call is
ambiguous:

  core.h(2211): error C2668: 'to_string_view': ambiguous call to overloaded
  function
  note: could be 'string_view N::to_string_view<T>(const T&)' [found using
  argument-dependent lookup]
  note: or 'basic_string_view<char> fmt::detail::to_string_view<T,0>(const T&)'

Both are reachable. format("{}", x) stores the argument through value's
constructor; to_string(x) passes it to detail::write unmapped, which lands on
the write() overload, as do FMT_COMPILE named fields and
nested_formatter::write_arg. Reverting either line alone breaks the build of
the test that covers it.

This turned up in Microsoft Office, which declares a constrained
to_string_view template next to its own string types. It only breaks where
those types are distinct classes, so the same code compiles on platforms whose
string types are std aliases - the ADL set is namespace std there and picks up
nothing.

One behaviour change worth noting: a non-template to_string_view in the
argument's namespace used to win outright at these call sites, so a type that
satisfies is_std_string_like via find_first_of and data() but has no size()
formatted through ADL and now fails to compile, because the trait only checks
that the qualified overload is viable, not that its body instantiates. That is
the removed extension point going away rather than a new restriction; ADL
to_string_view stopped being supported in 02bf4d1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@vitaut vitaut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding namespace qualification seems OK but the tests are an overkill, let's drop them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vitaut
vitaut merged commit e91e927 into fmtlib:main Sep 12, 2026
47 checks passed
@vitaut

vitaut commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants