How to declare a constructor of template class as friend with clang? (compiles with g++, not clang++)
Asked Answered
G

1

7

I have a template class with a private constructor which is to be friends of every typed instance of the class. The following compiles under g++ 11.4.0 but fails under clang++ version 14.0.0-1ubuntu1.1

template <typename T>
class foo {
    foo(T){}
    template <typename U> friend foo<U>::foo(U);

    public:
    foo(){}
};

int main() {
    foo<int> a{};
}

clang gives the error

main.cpp:4:34: error: missing 'typename' prior to dependent type name 'foo<U>::foo'
    template <typename U> friend foo<U>::foo(U);
                                 ^~~~~~~~~~~
                                 typename
main.cpp:4:46: error: friends can only be classes or functions
    template <typename U> friend foo<U>::foo(U);

I don't think foo<U>::foo is a dependent type name, but at any rate adding typename as suggested results in the error

main.cpp:4:55: error: friends can only be classes or functions
    template <typename U> friend typename foo<U>::foo(U);
Gujranwala answered 2/9, 2023 at 2:26 Comment(6)
I only upvoted because you are demonstrating a difference between gcc and clang. But actually you haven't demonstrated impetus for requiring the constructor to be a friend. Also, what's the difference between foo<T>::foo(T) and foo<U>::foo(U)? Most importantly, what code are you hoping to write in the constructor (or elsewhere) that requires it to be a friend?Grace
It looks like the "missing 'typename'" error goes away in clang 16. It might be a bug in the compiler or maybe an incomplete implementation of the C++20 rules. Do you want to focus your question on the "friends can only be classes or functions" error (which appears whether or not you use typename)?Sienkiewicz
Did a few more experiments, and the two messages might be related. The "missing 'typename'" error becomes "missing 'typename' prior to dependent type name foo<U>::foo; implicit 'typename' is a C++20 extension" in clang 16 if compiled as C++17. Also, if I change the parameter from U to const U&, clang starts complaining that it expected a semicolon instead of a parameter list. So an underlying issue looks like clang does not see the friend as a function declaration.Sienkiewicz
As a potential work around, is there any reason you wouldn't want to make the whole class a friend of itself? As in template <typename U> friend class foo<U>;?Impeccable
@Gujranwala "Clang's trunk also fails even with -std=c++20" -- Yes, but not with the "missing 'typename'" error. Which matches my experiment with clang 16. Which is why I asked if you want to drop the "missing 'typename'" error from the question. Not drop both errors, just the one that was resolved in 16.Sienkiewicz
@Gujranwala "The english may be imprecise" -- Since you've confirmed the prose is the error, I've corrected the English to match your code.Sienkiewicz
A
3

This appears to be a compiler bug in clang. If we look at the related case where the class template constructor being befriended belongs to a different non-template class:

template<class T>
struct X {
    X(T);
};
class C {
    template<class U> friend X<U>::X(U);
    C();
};
template<class T> X<T>::X(T) { C c; }
X<int> x(1);

This gives a clue to the behavior of clang; which accepts with a warning:

warning: dependent nested name specifier 'X::' for friend class declaration is not supported; turning off access control for 'C' [-Wunsupported-friend]

So, clang is missing support in this area and doesn't realize it (to at least be able to warn) in the case where the constructor being befriended belongs to a class template.

The simplest workaround is probably to follow the suggestion at https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42328#c4 and befriend the whole class:

    template <typename U> friend class foo;
Anaclitic answered 2/9, 2023 at 3:23 Comment(1)
@Jarod42 oops, thanks. I think gcc is fine then and we're left with just a bug in clang.Anaclitic

© 2022 - 2024 — McMap. All rights reserved.