views:

186

answers:

5

Hi,

is it possible to define multiple output operators for an enum? I want to use this

std::ostream& operator<< (std::ostream& os, my_enum e);

operator to (1) print a human readable text and to (2) convert it to some code for storing in a database.

Thanks

A: 

Sure why not? You would have to create an ostream derived class which implements writing to the database, much the same as ofstream writes to a file. The devil is in the details.

1800 INFORMATION
+2  A: 

You may take advantage of overloading by the first argument.

//For human-readable output
std::ostream& operator<< (std::ostream& os, my_enum e);

//For database;    note the absence VVV of & sign here
std::ostream& operator<< (databasefmt fmt, my_enum e)
{
   std::ostream& os = fmt.stream;
   // Write data to os
   // ...
   return os;
}

struct databasefmt{
   std::ostream& stream;
   databasefmt(std::ostream & s) : stream(s) {};
};

Then write stream modifier that converts the stream to wrapping databasefmt class, so that next output to that modified stream would be database output for you enum. The printing code would look like this:

output_stream << "DATA: "<< database_format << my_enum::Value << "END OF DATA" ;
// Type:   std::ostream   |   databasefmt   |            std::ostream          |

and the wrapper like this:

//Variable is needed to avoid confusing parentheses in output operators    
struct databasefmt_converter_t {} database_format;
// we can't return reference, so we just return fairly small instance of wrapper
databasefmt operator<< (std::ostream& os, databasefmt_converter_t const&)
{  return databasefmt(os);  }
Pavel Shved
How would `database_format` be implemented?
dalle
dalle
@dalle : Added example. By the way, "stream modifiers" a common technique and has several possible implementations, some of them differing from my example.
Pavel Shved
@dalle : ah, indeed, sorry. So, we have to drop references everywhere then.
Pavel Shved
Minor nitpick: Double underscores are reserved for the compiler (and its libraries). May I suggest using a postfix _t (as in databasefmt_convert_t)?
Andreas Magnusson
A: 

The preferred way is to use std::ios_base::xalloc and then std::ios_base::iword:

int getxalloc()
{
   static const int ix = std::ios_base::xalloc();
   return ix;
}

enum my_enum
{
   zero,
   one,
   two,
};

std::ostream& operator<<(std::ostream& os, my_enum e)
{
   switch (os.iword(getxalloc())
   {
   default:
   case 0:
      os << (int)e;
      break;
   case 1:
      switch (e)
      {
      case zero:
         os << "zero";
         break;
      case one:
         os << "one";
         break;
      case two:
         os << "two";
         break;
      default:
         os << "unknown";
         break;
      }
      break;
   }

   return os;
}

int main()
{
   my_enum e = one;
   std::cout.iword(getxalloc()) = 0;
   std::cout << e << "\n"; // will output "1"
   std::cout.iword(getxalloc()) = 1;
   std::cout << e << "\n"; // will output "one"
}

After that you could add some fancy manipulator of your own, instead of using std::ios_base::iword directly. Like this:

inline std::ios_base& format_my_enum_as_int(std::ios_base& ib)
{
   ib.iword(getxalloc()) = 0;
   return ib;
}

inline std::ios_base& format_my_enum_as_string(std::ios_base& ib)
{
   ib.iword(getxalloc()) = 1;
   return ib;
}

int main()
{
   my_enum e = one;
   std::cout << format_my_enum_as_int << e << "\n"; // will output "1"
   std::cout << format_my_enum_as_string << e << "\n"; // will output "one"
}
dalle
+3  A: 

Create wrappers which will return some object instead of ostream& which will handle printing. In your case it will object for printing humand-readable value and object for printing database code. Here's rough example which prints human-readable form and integer form. ostream_enum_wrapper_human class with its operator << is used for printing human-readable form, class ostream_enum_wrapper_int with its << is used for printing integer code. To switch from ostream& to wrapper, operator << (ostream&, wrappertag) is used, which wraps ostream object inside of wrapper and returns wrapped object. So next << operator is called on wrapper object, not on ostream&, and wrapper class knows how to print value.

#include <iostream>
using namespace std;
class ostream_enum_wrapper_human
{
    public:
    ostream& out;
    ostream_enum_wrapper_human(std::ostream& _out) : out(_out){}

};

class ostream_enum_wrapper_int
{
    public:
    std::ostream& out;
    ostream_enum_wrapper_int(std::ostream& _out) : out(_out){}
};


enum T{zero,one,two};

struct human_readable{} HumanReadable;
ostream_enum_wrapper_human operator << (ostream& out, human_readable){
    return ostream_enum_wrapper_human(out);
}

struct print_int{} PrintInt;
ostream_enum_wrapper_int operator << (ostream& out, print_int){
    return ostream_enum_wrapper_int(out);
}


ostream& operator << (ostream_enum_wrapper_human out, T t)
{
    switch(t) {
        case zero: out.out << "zero"; break;
        case one: out.out << "one"; break;
        case two: out.out << "two"; break;
    }

    return out.out;
}

ostream& operator << (ostream_enum_wrapper_int out, T t)
{
    return out.out << static_cast<int>(t);
}

int main()
{
    cout << HumanReadable << zero << PrintInt << zero << HumanReadable << two;
}

prints zero0two

maykeye
A: 

This solution is far from perfect; but there is no really nice one to your problem.

class MyClass {...};

namespace xml
{
    std::ostream& operator << (std::ostream& os, MyClass& c);
}

namespace text
{
    std::ostream& operator << (std::ostream& os, MyClass& c);
}

As you can see I put the stream operators in namespaces. As a result I have to include the namespace into the current one. This is simply done with a simple using declaration.

using namespace xml;

The trick is to put the using declaration in the smalest scope possible.

Sean Farrell