0

Unfortunately the behavior I want to figure out is relevant to multiple compilation units. So I cannot provide a simple example to run in ideone/coliru. But I did write up a minimal code example.

Note this code as written here does compile and work. My questions are about why certain changes made to it don't work, and how to properly structure this code to meet my goals.

Update: The secondary question has been answered by @dyp. Only the question in the title remains. Thanks!

header:

// header 

#include <iostream>
#include <sstream>
using namespace std;

struct stream {
    std::stringstream ss;
    string str() const;
};
class htmlstream : public stream {
};
class jsonstream : public stream {
};

template <typename STREAM>
class writer {
    STREAM s;
public: 
    template <typename T> friend writer<STREAM>& operator<<(writer<STREAM>&, const T&);
    int write();
};

template <typename T> htmlstream& operator<<(htmlstream& hs, const T& t) {
    hs.ss << "<div>" << t << "</div>";
    return hs;
}
template <typename T> jsonstream& operator<<(jsonstream& js, const T& t) {
    js.ss << "{ \"t\": " << t << " }";
    return hs;
}

// this solves undefined reference to writer<htmlstream>& op<< <int>() linker error
// I want to write an operator<< that is generic both in the parameter of writer and in
// the type T
template <typename T> writer<htmlstream>& operator<<(writer<htmlstream>& w, const T& t) {
    w.s << t;
    return w;
}

#if 0
// why doesn't this work? (I am just missing something simple I think)
template <typename T, typename STREAM> writer<STREAM>& operator<<(writer<STREAM>& w, const T& t) {
    w.s << t;
    return w;
}
#endif

implementation cpp (includes header):

string stream::str() const {
    return ss.str();
}

template <typename STREAM> int writer<STREAM>::write() {
    cout << "writing: " << s.str() << endl;
    return 0;
}

// instantiation of writer -- I need to have this otherwise linker error. I want this to be declared generally so I can cover all descendants of stream in one statement.
template class writer<htmlstream>;

calling code cpp (includes header):

int main() {
    writer<htmlstream> hw;
    hw << 4;

    cout << hw.write() << endl;
    return 0;
}

Question 1:

  • As you can see I have multiple derived streams (htmlstream and jsonstream). I don't want to have to write template class writer<htmlstream>; as well as template class writer<jsonstream>;. I would like to use enable_if and is_base_of to achieve this. How? Also, why does compilation fail if I write template<> class writer<htmlstream> instead?

Question 2 (answered in comments):

  • Notice the #if 0 commented template function that I attempted to write, which compiles but fails to match anything. I want to be able to specify an operator<< that works on all writer parameterized types as well as all right-hand-side argument types. Is this possible? How is it written? (What keywords do I use to Google this?)

(mini-question) I also would like to lift things out of the header into the implementation file if possible. But it seems like this cannot be done for many of the templates. Maybe I just need to try declaring these templates extern in the header?

Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • 1
    `template class writer;` is not a specialization, it is an explicit instantiation. – Constructor Mar 26 '14 at 21:34
  • In the future, write two separated questions – Manu343726 Mar 26 '14 at 21:34
  • @Manu343726 I thought about that but they both use the same code. – Steven Lu Mar 26 '14 at 21:35
  • Using macros and multiple compiler invocations as the shell command, you can do multiple TUs on coliru. – Johannes Schaub - litb Mar 26 '14 at 21:35
  • @Constructor The standardese for explicit instantiation *is* specialization, confusing as it may sound :) I distinctly remember STL talking about this in one of his episodes. – fredoverflow Mar 26 '14 at 21:36
  • @JohannesSchaub-litb Ah I see. By editing that command and using preprocessor tricks to turn `main.cpp` into two different files? – Steven Lu Mar 26 '14 at 21:37
  • 1
    There might be a confusion of terms. "template class writer;" is not a specialization. It is an explicit instantiation. But it *generates* or *instantiates* a specialization (which is not explicit). – Johannes Schaub - litb Mar 26 '14 at 21:37
  • Well, at the end of the day I just want a clean way to auto generate my template class instances for the `stream`'s derived classes. Can I do this? – Steven Lu Mar 26 '14 at 21:38
  • You know that templates should be written on the header file only, right? The template compilation cannot be performed on two distinct TUs (Because its two phases) – Manu343726 Mar 26 '14 at 21:40
  • @Manu343726 I'm fine with that. The mini-question isn't the important part. – Steven Lu Mar 26 '14 at 21:40
  • @StevenLu Why do you not want to write two simple lines of code `template class writer;` and `template class writer;`? – Constructor Mar 26 '14 at 21:42
  • @Constructor Because I figured it should be possible (because of the existence of `is_base_of` and how nicely this would work used with `stream`. If it isn't, then that's okay, too. (The underlying motivation is DRY code -- if the answer is that I can't do this, I'll just use a macro, or just write it out.) – Steven Lu Mar 26 '14 at 21:43
  • 1
    `template writer& operator<<(writer& w, const T& t)` This is not the function template you befriended. – dyp Mar 26 '14 at 21:46
  • @dyp How to reconcile this issue? Is my friend decl wrong or is the function decl wrong? – Steven Lu Mar 26 '14 at 21:50
  • Question 2 is http://www.parashift.com/c++-faq/template-friends.html – aschepler Mar 26 '14 at 21:52
  • 2
    @aschepler New, unified FAQ: http://isocpp.org/wiki/faq/templates#template-friends (probably "unified" like in "uniform initialization" ;) – dyp Mar 26 '14 at 21:54
  • @StevenLu [At the end of this answer](http://stackoverflow.com/a/22585725/420683), there are three examples of how to do it. – dyp Mar 26 '14 at 21:56
  • Thanks @dyp. Looks like I just needed to try a few more things – Steven Lu Mar 26 '14 at 21:57

1 Answers1

0

Honesty, I don't quite get what you're asking, why you need derived streams and what (useful) a writer does on top of a stream , but if I get what you want to do right, here is a simple way (live example):

template<typename tag>
struct stream {
    std::stringstream ss;
    std::string str() const { return ss.str(); };
};

struct html { };
struct json { };

template<typename T>
stream<html>& operator<<(stream<html>& hs, const T& t) {
    hs.ss << "<div>" << t << "</div>";
    return hs;
}

template<typename T>
stream<json>& operator<<(stream<json>& js, const T& t) {
    js.ss << "{ \"t\": " << t << " }";
    return js;
}

int main() {
    stream<html> hs;
    stream<json> js;
    hs << 4;
    js << 4;
    cout << hs.str() << endl;
    cout << js.str() << endl;
}

Output:

<div>4</div>
{ "t": 4 }

Just keep all template code in a header without separating definitions.

iavr
  • 7,547
  • 1
  • 18
  • 53
  • This doesn't address my questions. Could you write a little about what question your answer actually answers? (it just fills out various stuff that I had left out in my question for the sake of brevity.) In particular, the purpose of having different streams (but them not having any methods or members different) is simply that they all write into strings, what sets them apart are simply the `operator<<`s that they have. – Steven Lu Mar 27 '14 at 01:44
  • As I said, this doesn't really answer your questions; I just suspected that what you want to do may be done in a simpler way, in particular without stream inheritance and without `worker` at all, in which case your questions maybe do not apply at all. With this simplified approach, different streams indeed have no different methods or members, and the difference is only in `operator<<` exactly as you say. I am sorry if I have missed something else you want to do. – iavr Mar 27 '14 at 09:11
  • @StevenLu I think all problems originate from your attempt to separate template definitions in cpp files. [Here](http://coliru.stacked-crooked.com/a/0e1a86868f45dcb2) is a working version that is closer to your code, without these problems. – iavr Mar 27 '14 at 09:30
  • @StevenLu And [here](http://coliru.stacked-crooked.com/a/42e5bb7a0a791595) is a version that is even closer to your code, in that `writer`'s `operator<<` is a free function and not a member, with an additional function object `stream_writer` that is a friend of `writer`. – iavr Mar 27 '14 at 09:37
  • Yep, "all problems originate from your attempt to separate template definitions in cpp files" I think you're right on this. Also I hadn't realized I can make the operator<< a member func the returns *this. I had overlooked that as well. – Steven Lu Mar 27 '14 at 13:48