2

I want to implement a template function using nested-types of a template-class.

I have just read here that it is better to implement operator << as non-member and non-friend function. Therefore I decided to move functions toStream() and tableToStream() outside MyClass:

template <typename T>
class MyClass
{
public:
  typedef boost::dynamic_bitset<> BoolTable; 
  typedef std::vector<T>          MsgTable;
private:
  BoolTable  b_;
  MsgTable   m_;
public:
  const BoolTable& getB() const { return b_; }
  const MsgTable & getM() const { return m_; }

  std::ostream& toStream (std::ostream& os) const
  {
    os <<"Bool: ";  tableToStream (os, getB());  os <<'\n';
    os <<"Msg:";    tableToStream (os, getM());  os <<'\n';
    return os;
  }

  template <typename TABLE>
  std::ostream& tableToStream (std::ostream& os, const TABLE& table) const
  {
    for (int i=0; i < table.size(); ++i)
      os << table[i] <<',';
    return os;
  }
};

template <typename T>
std::ostream& operator << (std::ostream& os, const MyClass<T> mc)
{
  return mc.toStream(os);
}

It's easy to convert MyClass::toStream() into an operator << non-member and non-friend function:

template <typename T>
std::ostream& operator << (std::ostream& os, const MyClass<T>& mc)
{
  os <<"Bool: ";  mc.tableToStream (os, mc.getB());  os <<'\n';
  os <<"Msg:";    mc.tableToStream (os, mc.getM());  os <<'\n';
   return os;
}

But I want to use solely operator << instead of calling MyClass::tableToStream():

template <typename T>
std::ostream& operator << (std::ostream& os, const MyClass<T>& mc)
{
  os <<"Bool: "<< mc.getB() <<'\n';
  os <<"Msg:"  << mc.getM() <<'\n';
   return os;
}

For the function MyClass::tableToStream() I could use the following implementation, but this may mess the stream output because the function is too generic (any type can be TABLE).

template <typename TABLE>
std::ostream& operator << (std::ostream& os, const TABLE& table) 
{
  for (int i=0; i < table.size(); ++i)
    os << table[i] <<',';
  return os;
}

Therefore, I want to restrict to the nested types of MyClass. Below is one of my attempts to convert MyClass::tableToStream() into a standard operator << non-member and non-friend function:

template <typename T, typename MyClass<T>::TABLE>
std::ostream& operator << (std::ostream& os, const TABLE& table) 
{
  for (int i=0; i < table.size(); ++i)
    os << table[i] <<',';
  return os;
}

But the error is about typename MyClass<T>::TABLE.

Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200
  • Does the stream (OS) need to be a template parameter? It is usually just std::ostream. – Vaughn Cato Feb 20 '13 at 14:50
  • Your original `toStream()` doesn't reference anything from `MyClass`, not even `MsgTable`. It's rather unclear what you're trying to achieve. Are you sure `TABLE` is supposed to be a template parameter for `toStream()`? – Angew is no longer proud of SO Feb 20 '13 at 14:58
  • @VaughnCato, OS may also be std::ofstream or any other output stream. Please refer to the post http://stackoverflow.com/questions/236801/should-operator-be-implemented-as-a-friend-or-as-a-member-function/237111#237111 Cheers ;-) – oHo Feb 20 '13 at 15:22
  • @Angew, You are right, I have removed too much things (I thought the two `typedef ... *Table` was enough understandable...). I have updated my answer but keeping as small as possible the code. I hope everything is fine now ;-) – oHo Feb 20 '13 at 15:25
  • std::ofstream is derived from std::ostream, so you can still pass a std::ofstream to a function that takes a std::ostream&. – Vaughn Cato Feb 20 '13 at 15:25
  • @VaughnCato Yes you are right I could use `std::ostream`, but it is fine to implement a more generic `operator <<` allowing more output streams. What is the drawback of using a stream template parameter? Do you advice me to use `std::ostream` instead of stream template parameter? Thanks for sharing your experience and knowledge. Cheers ;-) – oHo Feb 20 '13 at 15:36
  • 1
    Since `operator<<` is used for more than streams, you run the risk of making your `operator<<` too generic if you make the stream be a template parameter as well. – Vaughn Cato Feb 20 '13 at 16:14
  • Thank you @VaughnCato to point out this risk... You have convinced me: I prefer to limit streams to `std::stream` heritage and avoid _non-understandable_ compilation errors in some years... – oHo Feb 20 '13 at 16:43

4 Answers4

1

Since you have clarified your question a lot, my first answer does not apply any more and I'll remove-edit it to give you something that might fit better:

Updated answer: You want to constrain the template to accept only types that are typedeffed inside your MyClass template. Such constraints are usually achieved by application of SFINAE, especially by std::enable_if (or boost::enable_if, if your library lacks that part of C++11 support). Sadly, there is no traits like a is_typedeffed_inside that could be used for your case. It's even worse: there is no way to write such a trait just using the plain typedefs, since there is nothing special about being typedeffed inside a given class - the compiler has no way to determine (and is not interested in) if a given known type has some alias name for it somewhere.

But if your typedefs are just the ones you show in your question, I have good news for you: you need exactly two operator<< for that:

  1. One for boost::dynamic_bitset<>, since that is the BoolTable for any MyClass instantiation.
  2. Another one, templated, for std::vector<T>, since that is the MsgTable for each corresponding MyClass<T>.

The downside is, that with this templated operator<<, you'd be able to output any std::vector<FooBar>, even if FooBar is completely unrelated to any use of MyClass. But that holds for any other possible implementation of the proper operator<<'s - if there's no explicit restriction on the MSG parameter, there's no restriction on a FooBar making a std::vector<FooBar> a viable MyClass<MSG>::MsgTable.

My conclusion for your question: you wanted to have the operator<< for its convenient looks, since it is normally used for thet purpose. In your case, you can provide it for MyClass<MSG> objects, but there is no way to do so for the inner typedefs alone.

I'd implement it that way:

template <class MSG>
class MyClass {
  /* ... */
public:
  // instead of declaring op<< a friend, redirect to a method that has 
  // natural access to private members
  std::ostream& printToStream(std::ostream& os) const
  {
    os << "Bool: "; 
    tableToStream (getB(), os); 
    os <<"\nMsg:";   
    tableToStream (getM(), os); 
    return os <<'\n';
  }
private:
  // make this one private so nobody can misuse it to print unrelated stuff
  template <class Table>
  static void tableToStream(Table const& table, std::ostream& os)
  {
    std::copy(begin(table), end(table), ostream_iterator(os, ", "));    
  }
};

template <typename MSG>
std::ostream& operator << (std::ostream& os, const MyClass<MSG>& mc)
{
  return mc.printToStream(os); 
}
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Yep, I want the `operator <<` uses solely the public types of `MyClass`. I am afraid the `operator <<` of your example accepts any other classes and may mess the stream output of my project. But I would like to know the _not-come-in-very-handy_ solutions. If that solution is too much unmaintanable, then I will rollback my changes... Cheers ;-) – oHo Feb 20 '13 at 15:42
1

Your original class is fine. It is true that if you want to have an operator << for writing to a stream that it should be a non-member non-friend function, like you have, but there is no reason that function can't call a public member function to do the work.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • But my member function `tableToStream` does not use other private members. Therefore `tableToStream` stuff can be moved outside `MyClass`. In fact, I realized that my question was unclear: I want to know how to use **nested-types** of a template-class as template parameter. I am updating that in my question (I could give a more basic source code...). Cheers ;-) – oHo Feb 20 '13 at 16:51
  • Hi, @ArneMertz explained me that the idea to use `typename`of the **nested-type** does not work. Therefore the solution in my case is to keep `tableToStream()` in `MyClass` as you suggested it. Thanks ;-) – oHo Feb 22 '13 at 15:53
0

I finally found this similar question

In my case the solution is:

template <typename T>
std::ostream& operator << (std::ostream& os, 
                           typename MyClass<T>::TABLE const& table) 
{
  for (int i=0; i < table.size(); ++i)
    os << table[i] <<',';
  return os;
}

UPDATE: As @ArneMertz pointed out, the above function does not work.
Below is the complete code I have tested:

#include <ostream>
#include <boost/dynamic_bitset.hpp>

template <typename T>
class MyClass  
{
  public:
    typedef boost::dynamic_bitset<> BoolTable; 
    typedef std::vector<T>          MsgTable;

    BoolTable  b_;
    MsgTable   m_;

    const BoolTable& getB() const { return b_; }
    const MsgTable & getM() const { return m_; }
};

template <typename T>
std::ostream& operator << (std::ostream& os, 
                           typename MyClass<T>::TABLE const& table) 
{
  for (int i=0; i < table.size(); ++i)
    os << table[i] <<',';
  return os;
}

template <typename T>
std::ostream& operator << (std::ostream& os, const MyClass<T>& mc)  
{
    os <<"Bool: "<< mc.getB() <<'\n'; // <-- this line is OK because it
    os <<"Msg:  "<< mc.getM() <<'\n';            //uses boost operator<<
    return os;
}

and the main function:

#include <iostream>

int main()
{
  MyClass<int> var;
  var.b_.push_back(true);
  var.b_.push_back(false);
  var.b_.push_back(true);
  var.m_.push_back(23);
  var.m_.push_back(24);
  var.m_.push_back(25);

  std::cout << var;
}
Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200
  • 1
    This does not really apply here, since MyClass has no member type named `TABLE` - at least not like you described it in the example code in the question. You have to specify either `MyClass::BoolTable` or `MyClass::MsgTable` - but you won't get the template working for both at the same time. – Arne Mertz Feb 21 '13 at 08:20
  • Hi @ArneMertz. I agree `TABLE` is not an existing type of `MyClass`. But in the function above, I use `TABLE` as a `typename`, therefore it can be any type of `MyClass`. Therefore, `MyClass::TABLE` can be either `MyClass::BoolTable` or `MyClass::MsgTable`. Please try to compile the code, you will see it works. I am updating my answer to provide you the complete code to test. Thank you for your help. Have fun, Cheers ;-) – oHo Feb 22 '13 at 15:14
  • My apologies @ArneMertz, you are 100% right. When I tested it yesterday, I thought it was OK, but it was using the `operator<<` from boost... Thank you very much for your relevant remark ;-). Therefore the solution in my case is to keep `tableToStream()` in `MyClass`. See you ;-) – oHo Feb 22 '13 at 15:51
-1

I believe you are confusing something. The typename is just to be able to seperate it from the other template-parameters. Try to rename it to

template <typename OS, typename MSG, typename MSGTable>
OS& operator << (OS& os, const MSGTable& table) const{}

and then use it like an object.

See here.

bash.d
  • 13,029
  • 3
  • 29
  • 42
  • -1: What is `TABLE` in your example, and how can the operator function be used with operator syntax when `MSG` and `MSGTable` can't be deduced? – Angew is no longer proud of SO Feb 20 '13 at 14:59
  • Did some corrections, besides I just copied the signature of the function. – bash.d Feb 20 '13 at 15:00
  • Hi @bash.d Thanks but I know how to write basic template functions. In the `operator <<` of your example, the `typename MSG` is not used. Moreover, the modifier `const` is allowed only for non-static member function, not in this case. Cheers ;-) – oHo Feb 20 '13 at 16:15