c++templatesc++17customization-point

hidden friend in a class template name clashes with another symbol in inline namespace


I'd like to provide a hidden friend for my type, and at the same time there is another object with the same name in an inline namespace. Everything works fine with normal types.

But if I have a template, the compiler errors when I instantiate the template

redefinition of 'cpo' as different kind of symbol

Here is the code:

namespace lib{

    struct CPO{
        template<typename... T>
        constexpr decltype(auto) operator()(T&&... args) const{
            cpo(static_cast<T&&>(args)...);
        }
    };

    inline namespace cpo_impl_{
        inline constexpr CPO cpo{};
    }

    struct Foo1{
        friend auto cpo(Foo1){
            return 5;
        }
    };

    template <typename T>
    struct Foo2{
        friend auto cpo(Foo2){
            return 6;
        }
    };
}

int main(){
    lib::Foo1 f1{};
    lib::cpo(f1); // works fine;

    lib::Foo2<int> f2{}; // error here
}

The smaller example is here

namespace lib{

    inline namespace impl_{
        inline constexpr int foo = 5;
    }

    template <typename T>
    struct Bar{
        friend auto foo(Bar){
            return 4;
        }
    };
}

int main(){
    lib::bar<int> b{};
}

btw, I am aware of other techniques such as tag_invoke but here I have no control of the names and the library


Solution

  • Your code is well-formed. This is Clang Bug 37556.

    Compiling this well-formed program with -std=c++2a:

    namespace X {
        inline namespace Y { int swap; }
    
        template<class>
        struct S {
            friend void swap(S&, S&) {}
        };
    }
    
    int main() {
        X::S<int> s1, s2;
        swap(s1, s2);
    }
    

    produces diagnostics (https://godbolt.org/g/ceWLxY):

    <source>:6:21: error: redefinition of 'swap' as different kind of symbol
            friend void swap(S&, S&) {}
                        ^
    <source>:11:15: note: in instantiation of template class 'X::S<int>' requested here
        X::S<int> s1, s2;
                ^
    <source>:2:30: note: previous definition is here
        inline namespace Y { int swap; }
                                ^
    1 error generated.
    

    Note that the program compiles successfully if S is replaced by a non-template class. Discussion on the CWG reflector verified that this program is well-formed, including Richard's statement "Oops, Clang's redeclaration check in the template instantiation case is incorrectly performing a redeclaration lookup as if for a qualified name here, rather than a redeclaration lookup for an unqualified name."

    No timeline for a fix however.


    As a workaround, you can wrap Foo2 in its own inline namespace

    inline namespace disambig_detail {
      template <typename T>
      struct Foo2{
        friend auto cpo(Foo2){
            return 6;
        }
      };
    }
    

    This should alter the declarative region enough to avoid the mis-diagnosis. Meanwhile name lookup should proceed pretty much the same as if the namespace wasn't there.