You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
STL: We should _STD qualify _Ugly function calls to avoid ADL #140
We've survived 20+ years with unqualified _Ugly function calls, but perhaps we should consider changing this. It's possible that this could improve throughput.
List of headers to process, as mentioned in #140 (comment) below (cvt subtree intentionally omitted):
That example is compelling, although the thought of changing all _Ugly function calls fills me with despair (more so than operator& and operator,). It seems that there are a lot of places that could accept elements of type Holder<Incomplete>* - not just algorithms, but also containers and their member functions. We use tag dispatch less frequently these days, but the number of function calls susceptible to ADL hijacking must extend far beyond _Get_unwrapped if I understand this correctly.
I am aware of (apparently) conformant code that instantly destroys any library it comes into contact with, but it is very pathological (e.g. highly poisoned operator, overloads). The element type here is fairly reasonable.
I think it's impossible to write a library that is immune to all variations on Holder<Incomplete>.
For example, you can't sort my array a — https://godbolt.org/z/FsV8tt — because sort inherently depends on being able to ADL-swap the element type, which you definitely can't ever do if any associated type is incomplete.
I've been trying to come up with an example where the library somewhere has to use an infix operator and we supply a class type without making it too contrived. The least-contrived thing I've come up with so far is
using T = Holder<Incomplete>*;
T t;
std::optional<T> ot;
Here t == t is OK, but ot == ot is a hard error.
std::vector<T> vt;
std::vector<std::optional<T>> vo;
std::find(vt.begin(), vt.end(), nullptr); // technically implementable, but nobody actually does
std::find(vo.begin(), vo.end(), nullptr); // literally unimplementable?
I believe it is impossible to accept std::find(vo.begin(), vo.end(), nullptr), because it has to be able to find std::optional's operator== via ADL in order to do its job but if it does ADL and tries to complete Holder<Incomplete> then it blows up.
Richard Smith has brought up similar issues in the past for places we can't fix with qualification, like operator overloads. I don't think there's anything reasonable the library can do to shut this problem down completely but qualifying our _Ugly things is probably still reasonable on the basis of improved diagnostics and/or throughput.
Looking at the long list of things attached to this, and:
Richard Smith has brought up similar issues in the past for places we can't fix with qualification, like operator overloads. I don't think there's anything reasonable the library can do to shut this problem down completely but qualifying our _Ugly things is probably still reasonable on the basis of improved diagnostics and/or throughput.
is there customer value to be gained here given that we are seemingly unable to shut the problem down completely?
EDIT: To clarify, this is more about the linked issues being marked as 'duplicates' of this one rather than this change itself.
We talked about this at the weekly maintainer meeting, and we've decided to qualify (with _STD, _RANGES, _CHRONO, etc.) every _Ugly function call, like what we currently do for non-_Ugly function calls. This will avoid ADL issues as much as possible, and (as bonuses) will improve throughput by some nonzero amount (potentially significant), and will sometimes avoid infinite recursion during lookup. We'll be able to enforce _STD etc. qualification the same way we do for non-_Ugly calls (where we have a near-perfect record of detecting issues during code review), and it doesn't impact code clarity too much.
However, I want to land the Standard Library Modules PR (which modifies many lines of every header) before accepting any PRs that attempt to qualify _Ugly function calls.
When we're ready to begin qualifying all such calls, this can be done in one or a handful of headers at a time - a single massive PR updating all headers atomically is not necessary (and would be a pain to review and keep up to date with main).
Casey Carter (@CaseyCarter) mentioned that we can make a checklist of headers in this issue, when we're ready to begin accepting PRs.
changed the title [-]STL: Should we _STD qualify _Ugly function calls to avoid ADL?[/-][+]STL: We should _STD qualify _Ugly function calls to avoid ADL[/+]on Jan 3, 2023
restrict ADL for swap to enumeration and class types
That would actually be philosophically safe (because non-enum/class types are primitive and therefore don't require any special swap semantics)... but at the same time it wouldn't be enough to solve the problem! Consider std::variant<std::vector<Holder<Incomplete>*>> v; v.swap(v), which would still try to swap std::vector<Holder<Incomplete>*> objects, do ADL because it's a class type, find Holder<Incomplete> as an associated type, and explode. So there are always some places that fundamentally can't be ADL-proofed. But FWLIW I'm still personally of the opinion that whatever can be ADL-proofed, should be. Whenever an STL facility explodes due to ADL, I should be able to point at a specific piece of it and say, "Look, there's literally no way to do this piece without ADL." It should never be a simple five-character fix.
I note that my first comment on this issue #140 (comment) was about optional<T>, and #140 (comment) is about using types like this as implementation details; and then this month we have LWG 3969 which is precisely about using optional<T> as an implementation detail (such that it's ADL-proof to call init.value(), but not ADL-proof to call *init as currently specified in [alg.fold]/10).
I think there should be a way to test Can unqualified call (so we can do constexpr dispatch for them). Unfortunately so far there is no way to do that.
Currently no implementation accepts this example due to ADL in the comparison of iterators (Godbolt link). However, the standard doesn't seem to state that this is UB/ill-formed.
I've put together a libclang-based script that qualifies calls automatically. It's not bulletproof (it has some false positives and false negatives), but it should automate some of this work. I'm test-driving it in #5629.
Script
#!/usr/bin/env pythonfromclang.cindeximportTranslationUnit, CursorKind, TokenKindfrompathlibimportPathfromsysimportargvimportredefis_interesting_identifier(identifier):
# These exceptions are functions deliberately called via ADL.exceptions= ["_Hash_equal", "_Verify_range", "_Same_sequence"]
returnre.match(r"^_[A-Z_]", identifier) andidentifiernotinexceptionsdeffully_qualified_namespace(cursor):
ret="::"whilecursorisnotNone:
ifcursor.kind==CursorKind.NAMESPACE:
ret=f"::{cursor.spelling}{ret}"cursor=cursor.semantic_parentreturnretdefcorrect_qualification(cursor):
ns=fully_qualified_namespace(cursor)
return {
"::std::" : "_STD ",
"::std::chrono::" : "_CHRONO ",
"::std::ranges::" : "_RANGES ",
"::stdext::" : "_STDEXT ",
}.get(ns, ns)
defmain():
source=Path(argv[1]).read_text(newline="") # No newline translation.tu=TranslationUnit.from_source(argv[1], args=argv[2:])
free_functions= {n.spelling : correct_qualification(n) fornintu.cursor.walk_preorder() if (
n.kindin [CursorKind.FUNCTION_TEMPLATE, CursorKind.FUNCTION_DECL] andn.semantic_parent.kindin [
CursorKind.NAMESPACE, CursorKind.TRANSLATION_UNIT, CursorKind.LINKAGE_SPEC
]
andis_interesting_identifier(n.spelling)
)}
tokens=list(tu.cursor.get_tokens())
corrections= [tfori, tinenumerate(tokens) if (
t.kind==TokenKind.IDENTIFIERandt.spellinginfree_functionsandt.cursor.kindnotin [
CursorKind.NAMESPACE, CursorKind.TRANSLATION_UNIT, CursorKind.LINKAGE_SPEC,
CursorKind.FUNCTION_DECL, CursorKind.FUNCTION_TEMPLATE, CursorKind.CXX_METHOD,
CursorKind.STRUCT_DECL, CursorKind.CLASS_DECL, CursorKind.UNION_DECL,
CursorKind.CLASS_TEMPLATE, CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION,
] and
(i==0ortokens[i-1].spellingnotin ["::", "_STD", "_CHRONO", "_RANGES", "_STDEXT", "_CSTD", ".", ">", "->"]) and
(i+1) <len(tokens) andtokens[i+1].spellingin ["<", "("]
)]
fortinreversed(corrections): # Work in reverse to not invalidate offsets.source=f"{source[:t.location.offset]}{free_functions[t.spelling]}{source[t.location.offset:]}"Path(argv[1]).write_text(source, newline="") # No newline translation.if__name__=="__main__":
main()
To try it locally: save that snippet as adl_proof.py in the project root. Run pip install libclang (the PyPi package is outdated and will install Clang 18, but that's okay, it works). Now you can run ./adl_proof.py <FILE> <CLANG_OPTIONS> to update <FILE>.
For example, you can update <chrono> with ./adl_proof.py stl/inc/chrono -xc++ -std=c++17 -Istl/inc. It might miss code that's ifdefed out, so to avoid that, I've been running it like for std in 11 14 17 20 23; do ./adl_proof.py <FILE> -xc++ -std=c++$std -D_ITERATOR_DEBUG_LEVEL=2 -Istl/inc; done.
It doesn't yet work with <complex> or anything in experimental/, but I've checked that if you run it on all the other headers under stl/inc, everything at least still compiles.
Activity
Quuxplusone commentedon Sep 27, 2019
Your
std::distancedoesn't trigger the issue; but anything that calls_Get_unwrappeddoes. For example,std::copy.https://godbolt.org/z/q5TeEO
StephanTLavavej commentedon Sep 27, 2019
That example is compelling, although the thought of changing all
_Uglyfunction calls fills me with despair (more so thanoperator&andoperator,). It seems that there are a lot of places that could accept elements of typeHolder<Incomplete>*- not just algorithms, but also containers and their member functions. We use tag dispatch less frequently these days, but the number of function calls susceptible to ADL hijacking must extend far beyond_Get_unwrappedif I understand this correctly.I am aware of (apparently) conformant code that instantly destroys any library it comes into contact with, but it is very pathological (e.g. highly poisoned
operator,overloads). The element type here is fairly reasonable.Quuxplusone commentedon Sep 27, 2019
I think it's impossible to write a library that is immune to all variations on
Holder<Incomplete>.For example, you can't
sortmy arraya— https://godbolt.org/z/FsV8tt — becausesortinherently depends on being able to ADL-swapthe element type, which you definitely can't ever do if any associated type is incomplete.I've been trying to come up with an example where the library somewhere has to use an infix operator and we supply a class type without making it too contrived. The least-contrived thing I've come up with so far is
Here
t == tis OK, butot == otis a hard error.I believe it is impossible to accept
std::find(vo.begin(), vo.end(), nullptr), because it has to be able to findstd::optional'soperator==via ADL in order to do its job but if it does ADL and tries to completeHolder<Incomplete>then it blows up.StephanTLavavej commentedon Sep 27, 2019
In that case, I think I want the LWG to tell us what to do.
BillyONeal commentedon Sep 30, 2019
Richard Smith has brought up similar issues in the past for places we can't fix with qualification, like operator overloads. I don't think there's anything reasonable the library can do to shut this problem down completely but qualifying our
_Uglythings is probably still reasonable on the basis of improved diagnostics and/or throughput.CaseyCarter commentedon Sep 14, 2022
Which option do we prefer:
_STD _Meow(/*...*/)[longer/hard to miss in code review](_Meow)(/*...*/)[shorter/easier to miss in code review]I don't list
::std::_Meow(/*...*/)as an option here because we recently relitigated "_STDvs.::std::" when Nicole joined the team.BillyONeal commentedon Sep 14, 2022
Looking at the long list of things attached to this, and:
is there customer value to be gained here given that we are seemingly unable to shut the problem down completely?
EDIT: To clarify, this is more about the linked issues being marked as 'duplicates' of this one rather than this change itself.
StephanTLavavej commentedon Sep 14, 2022
We talked about this at the weekly maintainer meeting, and we've decided to qualify (with
_STD,_RANGES,_CHRONO, etc.) every_Uglyfunction call, like what we currently do for non-_Uglyfunction calls. This will avoid ADL issues as much as possible, and (as bonuses) will improve throughput by some nonzero amount (potentially significant), and will sometimes avoid infinite recursion during lookup. We'll be able to enforce_STDetc. qualification the same way we do for non-_Uglycalls (where we have a near-perfect record of detecting issues during code review), and it doesn't impact code clarity too much.However, I want to land the Standard Library Modules PR (which modifies many lines of every header) before accepting any PRs that attempt to qualify
_Uglyfunction calls.When we're ready to begin qualifying all such calls, this can be done in one or a handful of headers at a time - a single massive PR updating all headers atomically is not necessary (and would be a pain to review and keep up to date with
main).Casey Carter (@CaseyCarter) mentioned that we can make a checklist of headers in this issue, when we're ready to begin accepting PRs.
frederick-vs-ja commentedon Sep 15, 2022
It's worthwhile to notice that
operator*involves ADL, and thus we should be more careful when using smart pointers as implementation details.😹5 remaining items
`_STD`-qualify `_Ugly` non-member function calls in `<variant>`
`_STD`-qualify `_Ugly` non-member function calls in `<variant>`
`_STD`-qualify `_Ugly` non-member function calls in `<variant>`
frederick-vs-ja commentedon Oct 17, 2022
Should we also_CSTD- or::-qualify _Ugly names (including intrinsics) in the global namespace?We should. Such qualifying is needed.
[-]STL: Should we _STD qualify _Ugly function calls to avoid ADL?[/-][+]STL: We should _STD qualify _Ugly function calls to avoid ADL[/+]achabense commentedon Aug 16, 2023
This is failing even after #3148. The problem is that there are intentional ADL
swaps. Is this a resolvable problem?frederick-vs-ja commentedon Aug 17, 2023
I don't think this is solvable, unless we change the standard wording to restrict ADL for
swapto enumeration and class types (likeranges::swap).Quuxplusone commentedon Aug 17, 2023
That would actually be philosophically safe (because non-enum/class types are primitive and therefore don't require any special swap semantics)... but at the same time it wouldn't be enough to solve the problem! Consider
std::variant<std::vector<Holder<Incomplete>*>> v; v.swap(v), which would still try to swapstd::vector<Holder<Incomplete>*>objects, do ADL because it's a class type, findHolder<Incomplete>as an associated type, and explode. So there are always some places that fundamentally can't be ADL-proofed. But FWLIW I'm still personally of the opinion that whatever can be ADL-proofed, should be. Whenever an STL facility explodes due to ADL, I should be able to point at a specific piece of it and say, "Look, there's literally no way to do this piece without ADL." It should never be a simple five-character fix.I note that my first comment on this issue #140 (comment) was about
optional<T>, and #140 (comment) is about using types like this as implementation details; and then this month we have LWG 3969 which is precisely about usingoptional<T>as an implementation detail (such that it's ADL-proof to callinit.value(), but not ADL-proof to call*initas currently specified in [alg.fold]/10).achabense commentedon Aug 17, 2023
I think there should be a way to test
Can unqualified call(so we can do constexpr dispatch for them). Unfortunately so far there is no way to do that.achabense commentedon Sep 7, 2023
ranges::swapis *slightly* better than unqualifiedswap:I'm curious, what's the advantage of
ADL-based swapoverpublic swapmethod?frederick-vs-ja commentedon Oct 21, 2023
Currently no implementation accepts this example due to ADL in the comparison of iterators (Godbolt link). However, the standard doesn't seem to state that this is UB/ill-formed.
It seems that iterators of standard containers shouldn't be implemented as class templates, and we can't fix this without breaking ABI.
1. Add _STD to defend ADL, towards microsoft#140,
YexuanXiao commentedon Jun 30, 2025
MSVC + STL now accepts it.
localspook commentedon Jun 30, 2025
I've put together a libclang-based script that qualifies calls automatically. It's not bulletproof (it has some false positives and false negatives), but it should automate some of this work. I'm test-driving it in #5629.
Script
To try it locally: save that snippet as
adl_proof.pyin the project root. Runpip install libclang(the PyPi package is outdated and will install Clang 18, but that's okay, it works). Now you can run./adl_proof.py <FILE> <CLANG_OPTIONS>to update<FILE>.For example, you can update
<chrono>with./adl_proof.py stl/inc/chrono -xc++ -std=c++17 -Istl/inc. It might miss code that'sifdefed out, so to avoid that, I've been running it likefor std in 11 14 17 20 23; do ./adl_proof.py <FILE> -xc++ -std=c++$std -D_ITERATOR_DEBUG_LEVEL=2 -Istl/inc; done.It doesn't yet work with
<complex>or anything inexperimental/, but I've checked that if you run it on all the other headers understl/inc, everything at least still compiles.AlexGuteniev commentedon Feb 4, 2026
We may want to cover
<flat_set>/<flat_maPtoo