1

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;
}
Veeno
  • 113
  • 2
  • 7
  • 1
    You should not use double-underscores in your variable names; they are reserved for implementation. For example, the implementation might `#define` macros of those names which would make your code behave strangely. – M.M May 18 '14 at 01:41
  • http://isocpp.org/wiki/faq/templates#template-friends – aschepler May 18 '14 at 01:42
  • 1
    This could be a version of the [template friends](http://www.parashift.com/c++-faq/template-friends.html) problem - try forward-declaring your function before the class definition that contains the friend. (maybe a typedef or three would keep things more readable!) – M.M May 18 '14 at 01:44
  • The error message for line `858` has all the parameters duplicated - this could be because it is not connecting the `friend` declaration with the actual function implementation: it matches parameters first for the class containing the friend template, and then for the friend template itself; and as explained by the FAQ this declares a different function to the non-friend function definition. – M.M May 18 '14 at 01:49
  • @MattMcNabb Nope, that doesn't fix it - look at my edit above. – Veeno May 18 '14 at 01:56
  • Can you post a test-case? (currently your example has `#include "xorshift.hpp"` but you have just described bits and pieces of this file) – M.M May 18 '14 at 02:06
  • @MattMcNabb [Here you go.](http://pastebin.com/kVQesY7C) Yes, it is horribly long, because C++11 doesn't have `static_if`. – Veeno May 18 '14 at 02:17
  • Also put an empty template argument list in the friend declaration ("`operator<< <> (`...") – aschepler May 18 '14 at 02:17
  • @aschepler Tried that, then it says `error: invalid use of template-id 'operator<< <>' in declaration of primary template` and doesn't recognise the outside function as a friend. – Veeno May 18 '14 at 02:22
  • @Veeno cut out all the parts which are not required to cause this compilation error, and post the text in your question. Should be able to get it down to under 50 lines. – M.M May 18 '14 at 02:28
  • @MattMcNabb: Interestingly enough, I also failed to see the issue, even if it is here in the pieces that were copied into the question!!!! – David Rodríguez - dribeas May 18 '14 at 02:40
  • @MattMcNabb I've added a simplified example in the second edit above. – Veeno May 18 '14 at 02:56

1 Answers1

2

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Great writeup. It's said that allowing `operator<< ` having access to `Tmpl` is bad for encapsulation, but this seems fairly insignificant to me, practically speaking: what's it going to do, create a new `Tmpl` and inspect the internals and ignore the argument? – M.M May 18 '14 at 02:47
  • 2
    I get the described error message with the forward declaration and the `istream` typo, and successful compilation with the forward declaration present and the typo fixed. – aschepler May 18 '14 at 02:53
  • I've added a simplified example giving me the same compile error in the second edit, it's at the bottom of the question post. – Veeno May 18 '14 at 02:54
  • 1
    @Veeno: Fix the return type, you are **not** returning a `std::basic_istream`. Also fix the second argument to be by reference in both cases. Since you are fixing things, fix all of the invalid identifiers in your code: you *cannot* use identifiers containing double underscores (i.e. `__a_`) or starting with lower underscore and a capital letter (i.e. `_CharT`) – David Rodríguez - dribeas May 18 '14 at 03:01
  • @DavidRodríguez-dribeas Oops, fixed that `istream` instead of `ostream` and added a missing `&`. Still gives the same error. (Not surprising, because I don't have these errors in my initial code.) The only reason I used that naming convention is because that's what I found in the random engines implementation in `bits/random.h` and `bits/random.tcc`. – Veeno May 18 '14 at 03:09
  • 1
    @Veeno: Seriously, **fix the `basic_istream`**, after you **really** fix it, it will compile (tested in g++ 4.8). Yes, that naming convention is the one you will find in the headers in the implementation, it is reserved for that purpose. The compiler and library can (and should) use that convention, user code (i.e. you) should not. – David Rodríguez - dribeas May 18 '14 at 03:20
  • @DavidRodríguez-dribeas ...Wow. I've looked at this for at least six hours straight now and did not see that stupid copy-paste mistake. I guess I should have just gone to sleep instead. Thanks for all the help! I will fix the identifiers as well. But first - sleep. XD – Veeno May 18 '14 at 03:26