1

I would like to find/implement a c++ (hopefully stl) compatible stream that supports multiple writing points. What I mean with multiple writing points is easy to explain with the following example. Lets say that you want to generate a source code file say in java language. When you get to the point where you want to write a line of code that have dependency on particular package - you could write:

stream << "import java.applet.*;";

stream << "public class MyApplet extends Applet {";
stream << "...";
stream << "}";

Note that you may already have other classes defined. The new class needs to go to the bottom, but the import needs to go to the top.

I am aware that I could solve this problem other ways. I think it will be cool if I can solve it with some stl stream pattern though.

Is boost tee with some filtering something that could work?

Note: the location could be based on analyzing the input or specifying dedicated type in this case I could have check if the string starts with import or I to write

stream << Import("java.applet.*);

where Import is a class that is accordingly serialized.

How you feel - is this something that could worth the effort?

gsf
  • 6,612
  • 7
  • 35
  • 64
  • First of all, I hope you realize that in order to do this properly, you'll basically have to parse the language. I also hope you realize that you can't just ignore newlines like your example shows. What happens when someone uses `stream << "\"";`? – Wug Jun 21 '13 at 23:31
  • Second, I'd like to ask what exactly you're doing writing a C++ program that generates a java program. There are certain exploitability problems that become possible when you give a program the power to write other programs based on user input. – Wug Jun 21 '13 at 23:32
  • This was just an example to explain the multiple writing points stream paradigm - I am not generating java with c++ ... but this is exactly what project like google protocol buffers does. – gsf Jun 21 '13 at 23:52
  • on First: I am looking for convenient way to solve the problem of serializing data that is not LL1 for generation. From the top of my head there are three reasonable option with data that is hard to define: It could go in a default "substream" or an exception could be thrown ... or it could be accumulated until its context become defined and then stored in the correct place. – gsf Jun 22 '13 at 00:00
  • How about just having multiple streams? Or are you hung up on having one object? – Yakk - Adam Nevraumont Jun 22 '13 at 01:15
  • The task is to convert a data model in several different formats - every with its own specifics (and probably different writing points). It will be very convenient for the rest of the design if it is a single class that implements the stl ostream interface. – gsf Jun 24 '13 at 15:37

1 Answers1

0

In order of increasing complexity:

First, just use different variables for each of the places you may want to write. Then stitch the data you put in each stream together when done.

As a second level of complexity, store a std::tuple< std::ofstream, std::ofstream, std::ofstream > straems, then use std::get<0>(streams) to get the first one. If you want names, use an enum { first_stream, second_stream_name, third_stream_name } and pass that to std::get.

For the most complex answer... well, it is a mess.

First Template metaprogramming boilerplate:

template<typename T, typename Tags, typename=void>
struct index_of {};
template<typename T, template<typename...>class Pack, typename Tag0, typename... Tags>
struct index_of<T, Pack<Tag0, Tags...>, typename std::enable_if< std::is_same<T, Tag0>::value >::type >
: std::integral_constant< int, 0 > {};
template<typename T, template<typename...>class Pack, typename Tag0, typename... Tags>
struct index_of<T, Pack<Tag0, Tags...>, typename std::enable_if< !std::is_same<T, Tag0>::value >::type >
: std::integral_constant< int, index_of<T, Pack<Tags...> >::value + 1 > {};

template<typename Src, template<typename...>class Pack>
struct copy_types {};

template<template<typename...>class Lhs, typename... Ts, template<typename...>class Target>
struct copy_types< Lhs<Ts...>, Target > {
  typedef Target<Ts...> type;
};
template<typename Src, template<typename...>class Pack>
using CopyTypes = typename copy_types<Src, Pack>::type;

template<typename Pack, typename T>
struct append {};
template<template<typename...>class Pack, typename... Ts, typename T>
struct append<Pack<Ts...>, T> {
  typedef Pack<Ts..., T> type;
};
template<typename Pack, typename T>
struct Append = typename append<Pack, T>::type;

template<template<typename...>class Pack, typename T, std::size_t N>
struct repeat {
  typedef Append< repeat< Pack, T, N-1 >::type, T > type;
};
template<template<typename...>class Pack, typename T>
struct repeat< Pack, T, 0 > {
  typedef Pack<> type;
};
template<template<typename...>class Pack, typename T, std::size_t N>
using Repeat = typename repeat<Pack, T, N>::type;

Now, because it is fun, a tagged tuple:

template<typename T, typename Tags>
struct type_based_map;

template<typename T, template<typename...>class Pack, typename... Tags>
struct type_based_map< T, Pack<Tags...> > {
  Repeat< std::tuple, T, sizeof...(Tags) > data;
  template<typename Tag>
  T& get() {
    return std::get< index_of< Tag, std::tuple<Tags...> >::value >( data );
  }
  template<typename Tag>
  T& get() const {
    return std::get< index_of< Tag, std::tuple<Tags...> >::value >( data );
  }
  template<typename... Args, typename=typename std::enable_if< sizeof...(Args) == sizeof...(Tags) >::type >
  explicit type_based_map( Args&&... args ):data( std::forward<Args>(args)... ) {}
  type_based_map( type_based_map&& ) = default;
  type_based_map( type_based_map const& ) = default;
  type_based_map( type_based_map& ) = default;
};

Now, down to the meat and potatoes. A poly-stream:

template<typename Tag, typename U>
struct TaggedData {
  U&& data;
  explicit TaggedData( U&& u ):data(std::forward<U>(u)) {}
  TaggedData(TaggedData &&) = default;
  TaggedData(TaggedData const&) = default;
  TaggedData(TaggedData &) = default;
};
template<typename Tag>
struct DataTagger {
  template<typename U>
  TaggedData<U> operator()( U&& u ) const {
    return {std::forward<U>(u)};
  }
};

template<typename base_stream, typename Tags>
struct tagged_stream: type_based_map< base_stream, Tags >
{
  using type_based_map< base_stream, Tags >::type_based_map< base_stream, Tags >;
};
template<typename base_stream, typename Tags, typename Tag, typename U>
auto operator<<( tagged_stream<base_stream, Tags>& stream, TaggedData<Tag, U> data )
  ->declval( stream.get<Tag>() << std::forward<U>(data.u) )
  { return ( stream.get<Tag>() << std::forward<U>(data.u) ); }

which, once the bugs are eliminated, gives you this syntax:

struct bob {};
struct harry {};
struct alice {};
static DataTagger<bob> Bob;
static DataTagger<harry> Harry;
static DataTagger<alice> Alice;

typedef tagged_stream< std::ofstream, std::tuple<bob, harry, alice> > multi_out;

multi_out os;
os.get<bob>().open("bob.txt");
os.get<harry>().open("harry.txt");
os.get<alice>().open("alice.txt");
os << Bob(7) << " is seven in Bob\n";
os << Harry("hello") << " in Harry\n";
os << Alice(3.14) << " is baked by Alice\n";

which may or may not be what you are looking for.

This is far from debugged, and probably doesn't compile yet.

Honestly? Just have a different variable per sub stream. You'll want to integrate them back together manually at the end anyhow.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524