3

The following program does not compile in MS Visual Studio 19.

#include <iostream>
#include <string>

template <typename T>
class A;

template <typename T>
std::ostream &operator <<( std::ostream &, const A<T> & );

template <typename T>
class A
{
private:
    T x;

public:
    A( const T &x ) : x( x ) {}

    friend std::ostream &::operator <<( std::ostream &, const A<T> & );
};

template <typename T>
std::ostream &operator <<( std::ostream &os, const A<T> &a )
{
    return os << "a.x = " << a.x;
}

int main()
{
    std::cout << A<std::string>( "Hello" ) << '\n';
}

The compiler says that operator << is not a function.

While the following program

#include <iostream>
#include <string>

template <typename T>
class A;

template <typename T>
std::ostream &f( std::ostream &, const A<T> & );

template <typename T>
class A
{
private:
    T x;

public:
    A( const T &x ) : x( x ) {}

    friend std::ostream &::f( std::ostream &, const A<T> & );
};

template <typename T>
std::ostream &f( std::ostream &os, const A<T> &a )
{
    return os << "a.x = " << a.x;
}

int main()
{
    f( std::cout, A<std::string>( "Hello" ) ) << '\n';
}

compiles successfully.

What is the reason of that the first program does not compile? Is it a bug of MS Visual Studio 19 or do I have missed something from the C++ 20 Standard?

According to the C++ 20 Standard (13.7.4 Friends)

(1.3) — if the name of the friend is a qualified-id and a matching function template is found in the specified class or namespace, the friend declaration refers to the deduced specialization of that function template (13.10.2.6), otherwise,

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 4
    Don't you need to declare the `operator<<` as a template too? Like: `template friend std::ostream& operator<<(std::ostream &, const A&);` – Ted Lyngmo Apr 21 '21 at 20:45
  • @Ted That's it to my knowledge also (and always was), I am pretty sure that's a duplicate question. – πάντα ῥεῖ Apr 21 '21 at 20:47
  • 1
    @TedLyngmo As I understand each specialization of the class has the corresponding deduced specialization of the operator. See the second program that compiles. – Vlad from Moscow Apr 21 '21 at 20:47
  • @VladfromMoscow Hmm... g++ fails to link both programs but clang++ compiles (and runs) both (all the way down to C++11) – Ted Lyngmo Apr 21 '21 at 20:53
  • @VladfromMoscow I did neither - I'm searching for my life to figure out why they behave differently. – Ted Lyngmo Apr 21 '21 at 21:03
  • @πάνταῥεῖ It is not a duplicate question. Read the C++ Standard. – Vlad from Moscow Apr 21 '21 at 21:04
  • 1
    @VladfromMoscow Does this beauty work? `friend std::ostream& ::operator<<<>( std::ostream &, const A&);` (it made g++ happy - but it seems msvc doesn't like it) – Ted Lyngmo Apr 21 '21 at 21:07
  • @TedLyngmo It is important that there is used the qualified name of the operator that does not require template arguments. See the second program. – Vlad from Moscow Apr 21 '21 at 21:08
  • @VladfromMoscow Yeah, I can't really see why there is a difference between those programs. Tricky. Got my vote anyway :-) – Ted Lyngmo Apr 21 '21 at 21:13
  • Looks like a MSVC bug to me. – HolyBlackCat Apr 21 '21 at 21:15
  • 1
    @HolyBlackCat ... and in [g++ too](https://godbolt.org/z/r4PaaMT71) - The quoted paragraph doesn't seem to have changed since [c++17](https://timsong-cpp.github.io/cppwp/n4659/temp.friend#1.3) ... so, found no clues there. – Ted Lyngmo Apr 21 '21 at 21:26
  • Giving up for Tonight. The only version I found working with g++/clang++/MSVC was `friend std::ostream& operator<<(std::ostream&, const A&);` (or combinations down to `friend std::ostream& operator<<<>(std::ostream&, const A&);`) ... Looking forward to see where this ends up. – Ted Lyngmo Apr 21 '21 at 21:57
  • @VladfromMoscow I know the standard to poorly but I was thinking: Isn't the name in the non-template declaration `friend std::ostream& operator<<(std::ostream&, const A&);` a qualified-id or have gotten that wrong? It doesn't explain the difference between how MSVC treats your `operator<<` and `f` (which bugs me), but still ... – Ted Lyngmo Apr 22 '21 at 14:51
  • 1
    @TedLyngmo No it is not a qualified-id. A qualified id looks like ::operator <<. So the compiler will search the name as it is already declared and if it finds a template function (and there is no non-template function with the same signature) it refers to the template function. – Vlad from Moscow Apr 22 '21 at 15:28

1 Answers1

0

Your program is defective; however there is indeed a bug (or two) in Visual Studio.

First, your friend declaration should be spelled with a <> to indicate that it is a template specialization that is the friend:

friend std::ostream &::operator <<<>( std::ostream &, const A<T> & );
//                                ^~

It is also acceptable to spell it with T explicitly (i.e. not inferring the template arguments):

friend std::ostream &::operator <<<T>( std::ostream &, const A<T> & );
//                                ^~~

Incidentally, gcc provides a helpful hint to make this change.

However MSVC will still erroneously reject the program; this appears to only happen templates of the specific operator<< where the class template argument is passed by reference, but not by value:

#include <iostream>
template<class> struct A;
template<class> struct B;
template<class T> std::ostream& f(std::ostream&, A<T>);
template<class T> std::ostream &operator<<(std::ostream&, A<T>);
template<class T> std::ostream& f(std::ostream&, B<T> const&);
template<class T> std::ostream &operator<<(std::ostream&, B<T> const&);
template<class T> struct A {
    friend std::ostream& ::f<T>(std::ostream&, A<T>);
    friend std::ostream& ::operator<<<T>(std::ostream&, A<T>);
};
template<class T> struct B {
    friend std::ostream& ::f<T>(std::ostream&, B<T> const&);
    friend std::ostream& ::operator<<<T>(std::ostream&, B<T> const&); // MSVC bug
};
template<class T> std::ostream& f(std::ostream& os, A<T>) { return os << "1"; }
template<class T> std::ostream& operator<<(std::ostream& os, A<T>) { return os << "2"; }
template<class T> std::ostream& f(std::ostream& os, B<T> const&) { return os << "3"; }
template<class T> std::ostream& operator<<(std::ostream& os, B<T> const&) { return os << "4"; }
int main() {
    f(std::cout, A<int>()) << ' ' << A<int>() << '\n';
    f(std::cout, B<int>()) << ' ' << B<int>() << '\n';
}

The fact that MSVC rejects only friend std::ostream& ::operator<<<T>(std::ostream&, B<T> const&) makes it clear that this is a compiler bug. gcc and clang accept and print 1 2\n3 4, as expected.

Another, similar defect in MSVC is that it will reject friend std::ostream& ::f<>(std::ostream&, A) (that is, using the injected-class-name to name A<T>).

Regarding the Standard, the language was changed substantially post-C++20 by P1787R6: Declarations and where to find them; in particular, it removes the language that you quote in your question. As such, I don't think you can use that as an argument for being allowed to omit the <> from the friend function template specialization declaration, since P1787R6 resolves a bunch of CWG DRs and so can be regarded as a post-publication correction.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Reread the quote from the C++ Standard I provided in my question. – Vlad from Moscow Apr 22 '21 at 17:26
  • What you are showing is described in other quote from the C++ 20 Standard " (1.1) — if the name of the friend is a qualified or unqualified template-id, the friend declaration refers to a specialization of a function template, otherwise," – Vlad from Moscow Apr 22 '21 at 17:33
  • @VladfromMoscow that numbered list is removed from the Standard post-C++20, as per the diff to which I linked. – ecatmur Apr 22 '21 at 18:58
  • AFAIK this list is present in the C++ 17 Standard. So it looks strange that 1) compilers behave differently and 2) the compatibility with the C++ 17 Standard is broken in the C++ 20 Standard. – Vlad from Moscow Apr 22 '21 at 19:10
  • 1
    @VladfromMoscow I don't think any compilers actually implemented C++ (03 through 20) in the way that you expect. So removing that list was a matter of bringing the Standard in line with implementation practice. – ecatmur Apr 22 '21 at 21:34
  • @VladfromMoscow interesting: this *only* happens to `operator<<` - every other operator is accepted by MSVC. – ecatmur Apr 22 '21 at 21:35
  • ah, I'm wrong - of course, MSVC did. So this is a case of implementation divergence, that hopefully will be resolved by MSVC fixing at least some of these issues. – ecatmur Apr 22 '21 at 21:39
  • 1
    Finally - your code is accepted by MSVC 19.22, rejected by 19.23. (per godbolt). So it's a recent bug, as well. – ecatmur Apr 22 '21 at 21:54