Skip to content

CWG3174 [basic.lookup.argdep] Handling of friends is unclear since P1787 #831

Description

@timsong-cpp

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).

struct A;
namespace ns { int f(A&&); }
struct A {
    friend int ns::f(A&&);
};

int x = f(A{});

There is implementation divergence in the handling of this example (GCC trunk accepts with -std=c++20 since this commit; others reject).

The author of P1787R6 confirmed that no change in behavior was intended.

See also CWG143.

Suggested resolution:

Activity

  1. cpplearner commented on Dec 19, 2025

    @cpplearner

    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.

  2. cpplearner commented on Dec 19, 2025

    @cpplearner

    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, S1 and S2 are associated entities in different namespaces, a function (template) f1 is a friend of S2, but its declaration is in a namespace associated with S1, 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 f1 and f2; Clang finds neither.)

  3. jensmaurer commented on Feb 19, 2026

    @jensmaurer
    Member

    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.cpp friend 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?

  4. cpplearner commented on Feb 24, 2026

    @cpplearner

    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
    • [...]
  5. jensmaurer commented on Apr 16, 2026

    @jensmaurer
    Member
  6. 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions