3

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

Hui
  • 571
  • 1
  • 3
  • 9

1 Answers1

3

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thanks for the answer. what would be a good workaround to make it compile on all of gcc, msvc, and clang as of now? – Hui Aug 19 '20 at 10:25
  • thanks again. that seems to fix the issue on clang. I didn't realise that nothing works on MSVC yet. (even my previous example without template) – Hui Aug 19 '20 at 10:39
  • 1
    @dfri - Good point. It's possible that that internal linkage could prove bothersome, I suppose. A named namespace would do the same. – StoryTeller - Unslander Monica Aug 19 '20 at 10:41
  • to make MSVC happy, I have to put the template struct inside a namespace , let's say `foo`, and I have to `using namespace foo` under `namespace lib` – Hui Aug 19 '20 at 12:21