Granting friendship to constructor/destructor of template class specialization - works under C++17, but fails under C++20
Asked Answered
D

1

12

I have found a situation where code compiles successfully under C++17, but FAILS under C++20.
This blocks us from upgrading our existing code design to the newer standard.
Why doesn't this compile under C++20? It seems like a weird breach of backwards-compatibility.

The errors occur when I try to grant friendship to a constructor/destructor of a template class specialization.

I am compiling using MSVC with Ninja.
Compiles successfully under C++17.
Under C++20, I receive these errors:

error C2838: '{ctor}': illegal qualified name in member declaration
error C2838: '{dtor}': illegal qualified name in member declaration

Here is a simplified reproduction of the code that causes the errors under C++20, but compiles successfully under C++17:

template<typename T, int V> class B {};

// specialization of class B
template<typename T> class B<T, 0> {
private:
    T _t;   // private data member
public:
    constexpr inline B(T* p, int i) noexcept;   // B constructor declaration
    virtual inline ~B() noexcept;   // B destructor declaration
};

// empty class with only private static data, no methods
class A {
private:
    static int x;   // private static variable
public:
    // ERRORS HERE IN C++20, but compiles successfully in C++17
    template<typename T> friend B<T, 0>::B(T*, int) noexcept;   // C++20 ERROR
    template<typename T> friend B<T, 0>::~B() noexcept;   // C++20 ERROR
};

int A::x = 0;   // global definition of private static variable

template<typename T>   // B constructor definition
constexpr inline B<T, 0>::B(T* p, int i) noexcept : _t(0) { A::x++; }

template<typename T> // B destructor definition
inline B<T, 0>::~B() noexcept { A::x++; }

int main() {
    A a;
    B<const int, 0> b(0, 0);
}

I know I can workaround this by granting friendship to the entire class template for B (including ALL specializations), but this is undesirable because we want to limit friendship to the smallest possible restriction.
Only one or two template specializations (out of dozens) actually require this friendship, and we don't want to grant friendship to the other template specializations if it is not necessary.

Decretive answered 18/3, 2021 at 4:2 Comment(12)
From cppreference: Friend declarations cannot refer to partial specializations, but can refer to full specializations:Poppas
@Poppas I believe that's referring to frend classes, not friend functions, so I'm not sure if it applies the same way here?Decretive
Not true. The passage literally start with Both function template and class template declarations may appear with the friend specifier...Poppas
@Poppas Okay, which standard is it referring to? Why does this compile successfully under C++17? That article must be missing some details.Decretive
Don't see this one specifically listed, but there have been several conformance improvements in MSVC lately, and also /std:c++latest turns /permissive- on by default.Elizebethelizondo
@Decretive It usually lists changes in the standard by marking it with since c++11 or since c++17 ect. so it's probably been there since prior to c++11 at least. Chances are that your code was non-conformant even if it did compile before.Poppas
@Elizebethelizondo and super, perhaps conformance is an issue, but this compiles on GCC 10 also, which makes me suspicious that something else is going on?Decretive
@Decretive The wording in temp.friend/7 states rather unequivocally that "friend declarations shall not declare partial specializations" (though the only example provided refers to a class, not function). I would suggest you add the language-lawyer tag to the question.Elizebethelizondo
When looking in to this similar (but since deleted) question I found this would compile with any member function except the constructor or destructor. The compiler may be looking for a type name and getting confused by the presence of the constructor/destructor (which don't have return types).Cranky
@Cranky lol that was the question that inspired the example code for this one.Decretive
I'm pretty sure this program is ill-formed also in C++17.Schuller
@Schuller In what ways? And how would you make it not ill-formed?Decretive
M
8

Apple clang 12.0.0 won't allow this even in C++17 (but apparently it compiles under GCC 10):

[timr@Tims-Pro:~/src]$ g++ --std=c++17 -c x.cpp
x.cpp:19:42: warning: dependent nested name specifier 'B<T, 0>::' for friend class declaration is not supported; turning off
      access control for 'A' [-Wunsupported-friend]
    template<typename T> friend B<T, 0>::B(T*, int) noexcept;   // C++20 ERROR
                                ~~~~~~~~~^
x.cpp:20:42: error: expected the class name after '~' to name the enclosing class
    template<typename T> friend B<T, 0>::~B() noexcept;   // C++20 ERROR
                                         ^
Marengo answered 18/3, 2021 at 4:16 Comment(1)
It seems to be working on GCC 10 and MSVC, godbolt.org/z/7vqj5v. Clang tends to fail.Garneau

© 2022 - 2024 — McMap. All rights reserved.