Repository navigation
CWG3174 [basic.lookup.argdep] Handling of friends is unclear since P1787 #831
Description
Activity
Before P1787, [basic.lookup.argdep]/(4.3) says
Any namespace-scope friend functions or friend function templates declared in classes with reachable definitions in the set of associated entities are visible within their respective namespaces even if they are not visible during an ordinary lookup.
And before P1103R3 Merging Modules, it says
Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup.
IIUC, the original intent of [basic.lookup.argdep]/(4.3) is to find non-member functions or function templates that are only declared in a friend declaration, because that's the only kind of "invisible" friends in C++98 (?).
But the wording became unclear when P1103R3 introduced modules, and P1787 further removed "namespace-scope" and "visible within their respective namespaces".
To understand what [basic.lookup.argdep]/4 is intended to say, we might need to address a corner case: if, in a function call,
S1andS2are associated entities in different namespaces, a function (template)f1is a friend ofS2, but its declaration is in a namespace associated withS1, and its declaration is not visible (because it's in a module that's not imported), should ADL find it?Example:
lib1-mod1.cpp:export module lib1.mod1; export namespace ns1 { struct S1 {}; }
lib1-mod2.cpp:export module lib1.mod2; import lib1.mod1; export namespace ns1 { template<class T> constexpr int f1(S1, T) { return 0; } template<class T> constexpr int f2(S1, T) { return 0; } }
lib2.cpp:export module lib2; import lib1.mod1; import lib1.mod2; export struct S2 { friend constexpr int ns1::f1<S2>(ns1::S1, S2); };
example.cpp:import lib1.mod1; // Note: does not import lib1.mod2 import lib2; static_assert(f1(ns1::S1{}, S2{}) == 0); // Should ADL find `f1`? static_assert(f2(ns1::S1{}, S2{}) == 0); // This should not find `f2`, presumably
(MSVC finds both
f1andf2; Clang finds neither.)I thought the intent of that rule was that we consider only those friends whose target scope is the innermost enclosing namespace scope of the class; thus, the
lib2.cppfriend doesn't contribute to ADL.Whether the implementation-level indirect import of lib1.mod2 makes those friends reachable by ADL appears to be a case where we have permissible implementation divergence; see [module.reach] p2.
Can you suggest an update to the core wording for the original concern?
Proposed wording:
Modify [basic.lookup.argdep]/4 as indicated:
Argument-dependent lookup finds all declarations of functions and function templates that
- [...]
- (4.2) are declared as a friend ([class.friend]) of any class with a reachable definition in the set of associated entities, and have the same innermost enclosing non-inline namespace scope as such a class definition or
- [...]
- changed the title
[-][basic.lookup.argdep] Handling of friends is unclear since P1787[/-][+]CWG3174 [basic.lookup.argdep] Handling of friends is unclear since P1787[/+]on Apr 16, 2026
Full name of submitter (unless configured in github; will be published with the issue): Tim Song
Reference (section label): [basic.lookup.argdep]
Link to reflector thread (if any):
Issue description:
(From this StackOverflow question.)
Since P1787R6, [basic.lookup.argdep]/4.2 says that "Argument-dependent lookup finds all declarations of functions and function templates that [...] are declared as a friend of any class with a reachable definition in the set of associated entities". This notably omits the limitation that the said friends are members of one of the associated namespaces (or, for that matter, are members of namespaces instead of classes).
There is implementation divergence in the handling of this example (GCC trunk accepts with
-std=c++20since this commit; others reject).The author of P1787R6 confirmed that no change in behavior was intended.
See also CWG143.
Suggested resolution: