5

In the sample code below, it shows that boost::tuple can be created implicitly from the first template argument. Because of that I am not able to write a << operator as it becomes ambiguous.

Also I don't understand why ostringstream& << float is also ambiguous. This does not have any implicit construction. Why does this also give ambiguous error?

#include <iostream>
#include <boost/tuple/tuple.hpp>
#include <sstream>
#include <string>

using namespace std;

class Myclass
{
};

typedef boost::tuple<int,float,Myclass> Mytuple;

ostringstream& operator<<(ostringstream& os_, Mytuple tuple_)
{
  float f = tuple_.get<1>();
  //os_ << (int)tuple_.get<0>(); // Error because int is implicitly converted into Mytuple. WHYY?
  //os_ << tuple_.get<1>();      // No Clue Why this is ambiguous.
  //os_ << tuple_.get<2>();      // Error because no matching operator. Fine.
  return os_;
}

int main()
{
  Mytuple t1;
  t1 = 3;      // Working because int is implicitly converted into Mytuple!! WHY?
  //t1 = 3.0f; // Error because no matching constructor. Fine.
  return 0;
}

Error Mesasge:

tupleTest2.C:18: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:

balki
  • 26,394
  • 30
  • 105
  • 151
  • `boost::tuple` already has an `operator<<` much like that. I don't see how that leads to the errors you're getting but it may be related. – Ben Jackson Sep 29 '11 at 07:23
  • Also `boost::tuple` has constructors for 0..n of the tuple elements, so yours has a constructor `Mytuple(int)` which makes it convertible from `int`. – Ben Jackson Sep 29 '11 at 07:24
  • It compiles fine for me -as it should- with gcc 4.5 and gcc 4.7 experimental. What compiler version are you using? – rodrigo Sep 29 '11 at 07:26
  • I tried commenting my operator function. I get a no matching operator error when I do `stream << tuple1`. – balki Sep 29 '11 at 07:27
  • @rodrigo did you try uncommenting any of the lines? It fails when I try to uncomment this line `os_ << tuple_.get<1>();` – balki Sep 29 '11 at 07:29

3 Answers3

4

The problem is not with the tuple, but with your operator. This works fine :

ostream& operator<<(ostream& os_, Mytuple tuple_)
{
    os_ << tuple_.get<0>(); // Error because int is implicitly converted into Mytuple. WHYY?
    os_ << tuple_.get<1>();      // No Clue Why this is ambiguous.
    //os_ << tuple_.get<2>();      // Error because no matching operator. Fine.
    return os_;
}

The problem is that the ostringstream inherit operator<< from ostream, which has this signature : ostringstream& operator<<(ostringstream& os_, Mytuple tuple_) is allowed. Then the

ostream& operator<<(ostream& os, T t)

(change T with all available types in c++, see operator<< reference page

EDIT

Here is a simplified example (without a tuple) :

ostringstream& operator<<(ostringstream& os_, Mytuple tuple_)
{
    const int i = tuple_.get<0>();
    os_ << i; // error in this line
    return os_;
}

and the error is now :

dfg.cpp: In function ‘std::ostringstream& operator<<(std::ostringstream&, Mytuple)’:
dfg.cpp:18: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
/usr/lib/gcc/i386-redhat-linux/4.3.0/../../../../include/c++/4.3.0/bits/ostream.tcc:111: note: candidate 1: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char, _Traits = std::char_traits<char>]
dfg.cpp:14: note: candidate 2: std::ostringstream& operator<<(std::ostringstream&, Mytuple)

The above error message says : it is not possible to choose between two operators operator<<(ostream&,...) and operator<<(ostringstream&,...). This also raises another question : why on earth do you needoperator<<(ostringstream&,...)`?

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • Can you explain what the problem was? – R. Martinho Fernandes Sep 29 '11 at 07:37
  • It works. But why does this work but `ostringstream` does not work? Actually I want this to work with my custom stream. – balki Sep 29 '11 at 07:39
  • I don't understand. Yes, ostream has operator<< for primitive types. Why should that be ambiguous with a custom operator which takes a custom class? – balki Sep 29 '11 at 07:45
  • Additional explanation added. If you still don't understand please write, and I'll extend the answer – BЈовић Sep 29 '11 at 08:07
  • @VJo Thanks. Its clear now. Actually the stream I am using is not ostringstream/ostream but a custom stream which is internally used by a custom persistant map to write the object to disk. I replaced the tuple with a simple struct and got things working for now. – balki Sep 30 '11 at 07:36
3

When you write

 os << tuple_.get<0>();

there is no function that matches both parameters. Instead the compiler has a choice to apply an implicit conversion on either parameter

std::ostream << int

or

std::ostringstream << MyTuple

The latter would happen with the boost::tuple constructor that can take any number of arguments up to number of tuple elements. (And with float it fails, because float is convertible to int.)

When overloading stream operators, use the base class as the left hand side (ostream or even basic_ostream<CharT, Traits>.


Edit: You could disambiguate the call by casting the first argument.

ostringstream& operator<<(ostringstream& os_, Mytuple tuple_)
{
  static_cast<std::ostream&>(os_) << tuple_.get<0>(); 
  static_cast<std::ostream&>(os_)  << tuple_.get<1>();      
  static_cast<std::ostream&>(os_)  << tuple_.get<2>();      // Error because no matching operator. Fine.
  return os_;
}

However, overloading the operator with ostringstream is still a bad idea, because it won't work with operator chaining.

MyTuple a, b;
ostringstream ss;
ss << a << ' ' << b;

will invoke:

1) ostringstream& operator<<(ostringstream& os_, Mytuple tuple_)

2) ostream& ostream::operator<<(char)

3) ostream& operator<<(ostream&&, boost::tuple<int,float,Myclass>

visitor
  • 1,781
  • 10
  • 7
1

All those people telling you to use ::std::ostream for the type instead of ::std::ostringstream are absolutely correct. You shouldn't be using ::std::ostringstream that way.

But my main beef with your code is the distressing lack of generality. It only works for one particular tuple type, and not all of them.

So I wrote an operator << for ::std::tuple in C++0x that works for any tuple who's members can be individually written using operator <<. It can probably be translated relatively easily to work with Boost's tuple type. Here it is:

template < ::std::size_t fnum, typename tup_type>
void print_fields(::std::ostream &os, const tup_type &val)
{
   if (fnum < ::std::tuple_size<tup_type>::value) {
      ::std::cerr << "Fred " << fnum << '\n';
      os << ::std::get<fnum, tup_type>(val);
      if (::std::tuple_size<tup_type>::value > (fnum + 1)) {
         os << ", ";
      }
      print_fields<fnum + 1, tup_type>(os, val);
   }
}

template < ::std::size_t fnum, typename... Elements>
class field_printer;

template <typename... Elements>
class field_printer<0, Elements...> {
 public:
   typedef ::std::tuple<Elements...> tup_type;

   static void print_field(::std::ostream &os, const tup_type &val) {
   }
};

template < ::std::size_t fnum, typename... Elements>
class field_printer {
 public:
   typedef ::std::tuple<Elements...> tup_type;

   static void print_field(::std::ostream &os, const tup_type &val) {
      constexpr auto tupsize = ::std::tuple_size<tup_type>::value;
      os << ::std::get<tupsize - fnum, Elements...>(val);
      if (fnum > 1) {
         os << ", ";
      }
      field_printer<fnum - 1, Elements...>::print_field(os, val);
   }
};

template <class... Types>
::std::ostream &operator <<(::std::ostream &os, const ::std::tuple<Types...> &val)
{
   typedef ::std::tuple<Types...> tup_type;
   os << '(';
   field_printer< ::std::tuple_size<tup_type>::value, Types...>::print_field(os, val);
   return os << ')';
}

This prints out the tuple as "(element1, element2, ...elementx)".

Omnifarious
  • 54,333
  • 19
  • 131
  • 194