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.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
Giffyguy
  • 20,378
  • 34
  • 97
  • 168
  • From [cppreference](https://en.cppreference.com/w/cpp/language/friend): *Friend declarations cannot refer to partial specializations, but can refer to full specializations:* – super Mar 18 '21 at 04:15
  • @super I believe that's referring to frend classes, not friend functions, so I'm not sure if it applies the same way here? – Giffyguy Mar 18 '21 at 04:19
  • Not true. The passage literally start with *Both function template and class template declarations may appear with the friend specifier...* – super Mar 18 '21 at 04:21
  • @super Okay, which standard is it referring to? Why does this compile successfully under C++17? That article must be missing some details. – Giffyguy Mar 18 '21 at 04:23
  • Don't see this one specifically listed, but there have been several [conformance improvements](https://learn.microsoft.com/en-us/cpp/overview/cpp-conformance-improvements?view=msvc-160) in MSVC lately, and also `/std:c++latest` turns `/permissive-` on by default. – dxiv Mar 18 '21 at 04:24
  • @Giffyguy 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. – super Mar 18 '21 at 04:26
  • @dxiv and super, perhaps conformance is an issue, but this compiles on GCC 10 also, which makes me suspicious that something else is going on? – Giffyguy Mar 18 '21 at 04:29
  • 2
    @Giffyguy The wording in [temp.friend/7](https://eel.is/c++draft/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. – dxiv Mar 18 '21 at 05:04
  • 1
    When looking in to this similar (but since deleted) [question](https://stackoverflow.com/questions/66661698/error-when-granting-friendship-to-constructor-destructor-of-a-specialized-templa) 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). – 1201ProgramAlarm Mar 20 '21 at 14:59
  • @1201ProgramAlarm lol that was the question that inspired the example code for this one. – Giffyguy Mar 20 '21 at 16:45
  • I'm pretty sure this program is ill-formed also in C++17. – dfrib Mar 21 '21 at 18:17
  • @dfrib In what ways? And how would you make it _not_ ill-formed? – Giffyguy Mar 22 '21 at 06:30

1 Answers1

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
                                         ^
Giffyguy
  • 20,378
  • 34
  • 97
  • 168
Tim Roberts
  • 48,973
  • 4
  • 21
  • 30