“ambiguous overload for 'operator<<'” *without* a catch-all overload

人盡茶涼 提交于 2019-12-11 04:56:59

问题


So I am trying to implement the xorshift PRNGs as a parameterised STL-style class from random, like e.g. std::mersenne_twister_engine, so I can use it with those quite convenient distributions from the random library etc.

Anyway, I have a problem overloading the operator<< and, frankly, I am completely stumped.

The class is parameterised like this:

template <size_t __n,
          int_least8_t __a, int_least8_t __b, int_least8_t __c,
          uint64_t __m>
class xorshift_engine
{
...

The overload is declared as a friend inside the class like this:

template <size_t __n_,
          int_least8_t __a_, int_least8_t __b_, int_least8_t __c_,
          uint64_t __m_,
          typename _CharT, typename _Traits>
friend std::basic_istream<_CharT, _Traits>&
operator<< (std::basic_ostream<_CharT, _Traits>& _os,
            const xorshift_engine<__n_, __a_, __b_, __c_, __m_>& _x);

and its implementation outside the class looks like this:

template <size_t __n,
          int_least8_t __a, int_least8_t __b, int_least8_t __c,
          uint64_t __m,
          typename _CharT, typename _Traits>
std::basic_ostream<_CharT, _Traits>&
operator<< (std::basic_ostream<_CharT, _Traits>& _os,
            const xorshift_engine<__n, __a, __b, __c, __m>& _x)
{
    ...
}

When I try to compile the following:

#include <iostream>
#include <random>
#include "xorshift.hpp"

using namespace std;

int main()
{
    xorshift1024star bip(2345);
    mt19937_64 bip2(2345);
    cout << bip << endl;
    cout << endl << bip2 << endl;
    return 0;
}

(xorshift1024star is just an instantiation of the xorshift_engine class:

typedef xorshift_engine<16, -31, 11, 30, 1181783497276652981ULL> xorshift1024star;

) I get this error (I replaced my username in the file paths with ---):

C:\Users\---\Documents\randgen.cpp: In function 'int main()':
C:\Users\---\Documents\randgen.cpp:11:7: error: ambiguous overload for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream<char>}' and 'xorshift1024star {aka xorshift_engine<16ull, -31, 11, 30, 1181783497276652981ull>}')
  cout << bip << endl;
       ^
C:\Users\---\Documents\randgen.cpp:11:7: note: candidates are:
In file included from C:\Users\---\Documents\randgen.cpp:3:0:
C:\Users\---\Documents\xorshift.hpp:898:1: note: std::basic_ostream<_CharT, _Traits>& operator<<(std::basic_ostream<_CharT, _Traits>&, const xorshift_engine<__n, __a, __b, __c, __m>&) [with long long unsigned int __n = 16ull; signed char __a = -31; signed char __b = 11; signed char __c = 30; long long unsigned int __m = 1181783497276652981ull; _CharT = char; _Traits = std::char_traits<char>]
 operator<< (std::basic_ostream<_CharT, _Traits>& _os,
 ^
C:\Users\---\Documents\xorshift.hpp:858:2: note: std::basic_istream<_CharT, _Traits>& operator<<(std::basic_ostream<_CharT, _Traits>&, const xorshift_engine<__n_, __a_, __b_, __c_, __m_>&) [with long long unsigned int __n_ = 16ull; signed char __a_ = -31; signed char __b_ = 11; signed char __c_ = 30; long long unsigned int __m_ = 1181783497276652981ull; _CharT = char; _Traits = std::char_traits<char>; long long unsigned int __n = 16ull; signed char __a = -31; signed char __b = 11; signed char __c = 30; long long unsigned int __m = 1181783497276652981ull]
  operator<< (std::basic_ostream<_CharT, _Traits>& os,
  ^
In file included from C:/mingw-w64/x86_64-4.9.0-posix-seh-rt_v3-rev1/mingw64/x86_64-w64-mingw32/include/c++/iostream:39:0,
                 from C:\Users\---\Documents\randgen.cpp:1:
C:/mingw-w64/x86_64-4.9.0-posix-seh-rt_v3-rev1/mingw64/x86_64-w64-mingw32/include/c++/ostream:602:5: note: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = xorshift_engine<16ull, -31, 11, 30, 1181783497276652981ull>] <near match>
     operator<<(basic_ostream<_CharT, _Traits>&& __os, const _Tp& __x)
     ^
C:/mingw-w64/x86_64-4.9.0-posix-seh-rt_v3-rev1/mingw64/x86_64-w64-mingw32/include/c++/ostream:602:5: note:   no known conversion for argument 1 from 'std::ostream {aka std::basic_ostream<char>}' to 'std::basic_ostream<char>&&'

For reference, this is how the overload is declared inside mersenne_twister_engine in bits/random.h:

template<typename _UIntType1,
         size_t __w1, size_t __n1,
         size_t __m1, size_t __r1,
         _UIntType1 __a1, size_t __u1,
         _UIntType1 __d1, size_t __s1,
         _UIntType1 __b1, size_t __t1,
         _UIntType1 __c1, size_t __l1, _UIntType1 __f1,
         typename _CharT, typename _Traits>
friend std::basic_ostream<_CharT, _Traits>&
operator<<(std::basic_ostream<_CharT, _Traits>& __os,
           const std::mersenne_twister_engine<_UIntType1, __w1, __n1,
           __m1, __r1, __a1, __u1, __d1, __s1, __b1, __t1, __c1,
           __l1, __f1>& __x);

and its implementation in bits/random.tcc:

template<typename _UIntType, size_t __w,
         size_t __n, size_t __m, size_t __r,
         _UIntType __a, size_t __u, _UIntType __d, size_t __s,
         _UIntType __b, size_t __t, _UIntType __c, size_t __l,
         _UIntType __f, typename _CharT, typename _Traits>
std::basic_ostream<_CharT, _Traits>&
operator<<(std::basic_ostream<_CharT, _Traits>& __os,
           const mersenne_twister_engine<_UIntType, __w, __n, __m,
           __r, __a, __u, __d, __s, __b, __t, __c, __l, __f>& __x)
{
  ...
}

If I comment out the cout << bip << endl; line in my test code, it compiles and runs without error, so this overload is fine, but mine is not.

I am completely out of ideas. What am I missing? I would be more than grateful if someone could help me.


edit: For those suggesting it's the "template friends" problem which I should solve by forward-declaring the function, I have just added

template <size_t __n,
          int_least8_t __a, int_least8_t __b, int_least8_t __c,
          uint64_t __m>
class xorshift_engine;

template <size_t __n,
          int_least8_t __a, int_least8_t __b, int_least8_t __c,
          uint64_t __m,
          typename _CharT, typename _Traits>
std::basic_ostream<_CharT, _Traits>&
operator<< (std::basic_ostream<_CharT, _Traits>& _os,
            const xorshift_engine<__n, __a, __b, __c, __m>& _x);

before the class body/implementation, and I get the same error upon attempting to compile.


edit2: Simplified example giving the same compile error:

//file "failclass.hpp"

#ifndef _FAILCLASS_HPP
#define _FAILCLASS_HPP

#include <iosfwd>
#include <type_traits>

template <int n>
class failclass
{
private:
    static constexpr int k = n;

public:
    template<int n1, typename _CharT, typename _Traits>
    friend std::basic_istream<_CharT, _Traits>&
    operator<< (std::basic_ostream<_CharT, _Traits>& _os,
                const failclass<n1>& _x);
};

template<int n1, typename _CharT, typename _Traits>
std::basic_ostream<_CharT, _Traits>&
operator<< (std::basic_ostream<_CharT, _Traits>& _os,
            const failclass<n1>& _x)
{
    _os << _x.k;
    return _os;
}

#endif // _FAILCLASS_HPP

Try to compile this:

//file "failtest.cpp"

#include <iostream>
#include "failclass.hpp"

using namespace std;

int main()
{
    failclass<5> a;
    cout << a;
    return 0;
}

回答1:


Leaving the rest of the answer below for its value, but the particular error is just a typo:

template <size_t __n_,
              int_least8_t __a_, int_least8_t __b_, int_least8_t __c_,
              uint64_t __m_,
              typename _CharT, typename _Traits>
    friend std::basic_istream<_CharT, _Traits>&
//                    ^     -- you probably meant std::ostream!!!!!
    operator<< (std::basic_ostream<_CharT, _Traits>& _os,
                const xorshift_engine<__n_, __a_, __b_, __c_, __m_>& _x);

The template defined at namespace level and the friend take exactly the same arguments, but have a different return type.


Most of the following is not a direct answer to your error message, but tackles the root problem that got you into it. Personally I believe that the forward declaration should have fixed it. If it doesn't, you should provide a SCCE.

For the sake of discussion, lets simplify a bit the code to a template with a single argument for which you want to implement operator<<. The friend declaration:

template <typename T>
class Tmpl {
    friend std::ostream& operator<<(std::ostream&, Tmpl const &);
};

Provides a declaration for a non-template standalone function operator<< taking a std::ostream& and a Tmpl<T> const &. There is an important detail here, this is not befriending a template, or any free function. Given a specialization Tmpl<int>, the declaration befriends a function in the same namespace with the following signature:

std::ostream& operator<<(std::ostream& out, Tmpl<int> const & obj) {
   // I can access Tmpl<int> internals!
   return out;
}

While this is a possible approach, you most probably don't want to have to manually provide a different free function for each specialization of your template.

At this point there is a really interesting feature of a friend declaration: you can provide the definition inside the class, together with the friend declaration:

template <typename T>
class Tmpl {
    friend std::ostream& operator<<(std::ostream out, Tmpl const & t) {
       // definition goes here
       return out;
    }
};

The interesting point here is that, for each specialization of Tmpl, the compiler will generate for you a non-template free function that has access to the internals of the type.

Now, in this particular case, you might want to consider other alternatives. The first that comes to mind, as I often use it, is not to make operator<< a friend, but to provide a print function (to which you can add other arguments to control output), and then implement operator<< in terms of the public interface by calling print. You can opt to still make operator<< a friend that is defined inside the class [1], or you can provide a templated operator<< that calls print:

template <typename T>
class Tmpl {
public:
   std::ostream& print(std::ostream& out) const;
   // option 1:
   friend std::ostream& operator<<(std::ostream& out, Tmpl const & obj) {
       return obj.print(out);
   }
};
// option 2:
template <typename T>
std::ostream& operator<<(std::ostream& out, Tmpl<T> const & obj) {
   return obj.print(out);
}

A different alternative (which I don't recommend, but well, for the sake of completeness) is to declare the template above a friend:

template <typename T>
class Tmpl {
public:
   template <typename U>
   friend std::ostream& operator<<(std::ostream& out, Tmpl<U> const & obj);
};
// template defined as option 2 above

But this is a bad idea (and the alternative you opted for), as operator<< <int> will have access to Tmpl<double>, and it is quite easy to break encapsulation.

A slightly better alternative is to befriend only the specialization of the above template that exactly matches the arguments, but this is a bit more convoluted in code:

template <typename T> class Tmpl;
template <typename T>
std::ostream& operator<<(std::ostream&, Tmpl<T> const &);
template <typename T>
class Tmpl {
    friend std::ostream& operator<< <T>(std::ostream&, Tmpl<T> const &);
};

1 The possible reason to still make operator<< a friend even if it is implemented only in terms of the public interface is that it hides the operator from normal lookup, and makes it available only for ADL.



来源:https://stackoverflow.com/questions/23717751/ambiguous-overload-for-operator-without-a-catch-all-overload

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!