Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 238
Giffyguy Avatar asked Mar 18 '21 04:03

Giffyguy


1 Answers

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
                                         ^
like image 122
Tim Roberts Avatar answered Sep 22 '22 08:09

Tim Roberts