Domanda

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:

È stato utile?

Soluzione

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&,...)`?

Altri suggerimenti

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>

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)".

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top