Overloading operator<< for template instantiations: CRTP: error C2995: ... function template has already been defined

Say you want to define a class template, Foo<T>, whose instantiations:

1. have a string representation
2. define operator<< so that it can be directed to cout (console).

How should this template class be defined?

The following definition works but has some drawbacks:
Q2. You have to define a new operator<<(ostream& os, const Foo<T>& _type) overload for additional type parameter.
Q3. The implementation of operator<<(ostream& os, const Foo<T>& _type) are very similar: T should have a string representation, and string representations of Foo<T>, for all T, are the same.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <string>
#include <sstream>

using std::string;
using std::stringstream;
using std::cout;
using std::endl;
using std::ostream;

// Bar is a user-defined type of no significance; just a client class to be used to instantiate Foo.
struct Bar {
    int barNum;

    Bar(int b) : barNum(b) {}

    friend std::ostream& operator<<(std::ostream& os, const Bar& obj)
    {
        return os << "barNum:" << obj.barNum;
    }
};

// Class template of interest:
// Q1. How to define this template class so that Foo<int>, Foo<string>, Foo<Bar> instantiations have string representations?
template <typename T>
struct Foo
{
    int fooNum = 0;
    T val;

    Foo(int someFooNum, T someVal) : fooNum(someFooNum), val(someVal) {}

    template <typename U>
    friend std::ostream& operator<<(std::ostream& os, const Foo<U>& t_Foo);
};

// Use: os << Foo<int>
ostream& operator<<(ostream& os, const Foo<int>& t_int)
{
    std::stringstream ss;
    ss << "Foo<int>(" << t_int.fooNum << "," << t_int.val << ")";
    os << ss.str();
    return os;
}

// Use: os << Foo<string>
ostream& operator<<(ostream& os, const Foo<string>& t_str)
{
    std::stringstream ss;
    ss << "Foo<string>(" << t_str.fooNum << "," << t_str.val << ")";
    os << ss.str();
    return os;
}

// Use: os << Foo<Bar>
ostream& operator<<(ostream& os, const Foo<Bar>& t_bar)
{
    std::stringstream ss;
    ss << "Foo<Bar>(" << t_bar.fooNum << "," << t_bar.val << ")";
    os << ss.str();
    return os;
}

void Example()
{
    Foo<int> f_int(1, -10);
    cout << f_int;
    cout << endl;

    Foo<string> f_str(2, "Alice");
    cout << f_str;
    cout << endl;

    Foo<Bar> f_bar(3, Bar(30));
    cout << f_bar;
    cout << endl;
}

int main()
{
    Example();
}

Foo<int>(1,-10)
Foo<string>(2,Alice)
Foo<Bar>(3,barNum:30)

I tried to address Q2. and Q3. by using the Commonly-Recurring Template Pattern (CRTP) to address the duplication of the operator<< definitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct Foo
{
    int fooNum = 0;

    Foo(int someFooNum) : fooNum(someFooNum) {}
};

template <typename T>
struct FooCRTP : public Foo
{
    T val;

    FooCRTP(int someFooNum, T someVal) : Foo(someFooNum), val(someVal) {}

    template <typename T>
    friend ostream& operator<<(ostream& os, const FooCRTP<T>& t_Foo)
    {
        stringstream ss;
        ss << "Foo<T>(" << t_Foo.fooNum << "," << val << ")";
        os << ss.str();
        return os;
    }
};

void Example()
{
    FooCRTP<int> f_int(1, -10);
    cout << f_int;
    cout << endl;

    FooCRTP<string> f_str(2, "Alice");
    cout << f_str;
    cout << endl;

    FooCRTP<Bar> f_bar(3, Bar(30));
    cout << f_bar;
    cout << endl;
}

But I get the following compiler error:
1>D:\...\Scrapcode\ex_Templates\ex_Templates.cpp(41,21): error C2995: 'std::ostream &operator <<(std::ostream &,const FooCRTP<T> &)': function template has already been defined
1>D:\...\ex_Templates\ex_Templates.cpp(41,21): message : see declaration of 'operator <<'
1>D:\...\ex_Templates\ex_Templates.cpp(57,27): message : see reference to class template instantiation 'FooCRTP<std::string>' being compiled
1>D:\...\ex_Templates\ex_Templates.cpp(62,5): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'FooCRTP<Bar>' (or there is no acceptable conversion)

I think the compiler error is saying that I'm breaking the One-Definition-Rule with the redefinition of operator<< for FooCRTP<std::string> but I don't see where that happens.
How should this template class be defined?

You could define it like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename T> 
  struct foo
  {
    T t;
    int n; 
    
    explicit foo(int n, T t)
      : t{std::move(t)}
      , n{n} 
    {}
    
    // Note this is neither a member nor a function template
    friend std::ostream& operator<<(std::ostream& os, foo const& f)
    {
      stringstream ss;
      ss << "Foo<T>(" << f.n << "," << f.t << ")";
      os << ss.str();
      return os;
    }
  };
Example: https://coliru.stacked-crooked.com/a/b3f4912954d44d1e

Q2. You have to define a new operator<<(ostream& os, const Foo<T>& _type) overload for additional type parameter.
Not true: the alternative would be to define the function template declared on line 33. The functions on lines 38, 47, 56 are unrelated non-friend non-members. This suggests another way to address Q1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Class template of interest:
// Q1. How to define this template class so that Foo<int>, Foo<string>, Foo<Bar> instantiations have string representations?
template <typename T>
struct Foo
{
    int fooNum = 0;
    T val;

    Foo(int someFooNum, T someVal) : fooNum(someFooNum), val(someVal) {}

    // Note: use "U" not "T", since "T" belongs to the enclosing template Foo
    template <typename U>
    friend std::ostream& operator<<(std::ostream& os, const Foo<U>& t_Foo);
};

// Defines the template declared on line 33
template <typename U>
  std::ostream& operator<<(std::ostream& os, const Foo<U>& t_Foo)
  {
    std::stringstream ss;
    ss << "Foo<U>(" << t_Foo.fooNum << "," << t_Foo.val << ")";
    os << ss.str();
    return os;
  }
Example: https://coliru.stacked-crooked.com/a/6949005f9df9ac26

BTW, the curiously recurring template pattern is a little different. It always involves a base class template and looks like this:
1
2
3
template <typename Derived>
  class base; 
struct derived: base<derived>; 
This is an example of the CRTP because the base<T> can rely on the assumption that T is derived from base<T>.

Last edited on
Thanks for the clarification, mbozzi. You're right - I didn't correctly apply CRTP and it's not needed here for what I'm trying to do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

// Bar is a user-defined type of no significance; just a client class to be used to instantiate Foo.
struct Bar
{
    int barNum;

    Bar() = default;

    Bar(int b) : barNum(b) {}

    friend std::ostream& operator<<(std::ostream& os, const Bar& obj)
    {
        return os << "barNum:" << obj.barNum;
    }
};

template <typename T>
struct Foo
{
    int fooNum = 0;
    T val;

    Foo(int someFooNum, T someVal) : fooNum(someFooNum), val(someVal) {}

    // Method 1: Define a templated operator<< overload
    template <typename U>
    friend ostream& operator<<(ostream& os, const Foo<U>& t_Foo);
};

// Method 1
template <typename U>
ostream& operator<<(ostream& os, const Foo<U>& t_Foo)
{
    stringstream ss;
    ss << "Foo<U>(" << t_Foo.fooNum << "," << t_Foo.val << ")";
    os << ss.str();
    return os;
}

template <typename T>
struct Fooz
{
    int foozNum = 0;
    T val;

    Fooz(int someFooNum, T someVal) : foozNum(someFooNum), val(someVal) {}

    // Method 2: Define a non-templated operator<< overload. 
    friend ostream& operator<<(ostream& os, const Fooz& t_Fooz)
    {
        stringstream ss;
        ss << "Fooz<U>(" << t_Fooz.foozNum << "," << t_Fooz.val << ")";
        os << ss.str();
        return os;
    }
};

void Example()
{

    Foo<int> f_int(1, -10);
    cout << f_int;
    cout << endl;

    Foo<string> f_str(2, "Alice");
    cout << f_str;
    cout << endl;

    Foo<Bar> f_bar(3, Bar(30));
    cout << f_bar;
    cout << endl;

    Fooz<Bar> fz_bar(4, Bar(40));
    cout << fz_bar;
    cout << endl;
}

int main()
{
    Example();
}

Foo<U>(1,-10)
Foo<U>(2, Alice)
Foo<U>(3,barNum:30)
Fooz<U>(4,barNum:40)


Incidentally, there is a discussion on SO about it, here [1].

For completeness, added some preprocessor magic to get the template types as string [2], [3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

#define TINFO(type)                    \
template <typename T> struct TypeName; \
template <>                            \
struct TypeName<type>                  \
{                                      \
    static const char* AsString()      \
    {                                  \
        return #type;                  \
    }                                  \
};                                     \

struct Bar;
TINFO(string);
TINFO(int);
TINFO(Bar);

// Bar is a user-defined type of no significance; just a client class to be used to instantiate Foo.
struct Bar
{
    int barNum;

    Bar() = default;

    Bar(int b) : barNum(b) {}

    friend std::ostream& operator<<(std::ostream& os, const Bar& obj)
    {
        return os << "barNum:" << obj.barNum;
    }
};


// Q1. How to define this template class so that Foo<int>, Foo<string>, Foo<Bar> instantiations have string representations?
template <typename T>
struct Foo
{
    int fooNum = 0;
    T val;

    Foo(int someFooNum, T someVal) : fooNum(someFooNum), val(someVal) {}

    // Method 1: Define a templated operator<< overload
    template <typename U>
    friend ostream& operator<<(ostream& os, const Foo<U>& t_Foo);
};

template <typename U>
ostream& operator<<(ostream& os, const Foo<U>& t_Foo)
{
    stringstream ss;
    ss << "Foo<" << TypeName<U>::AsString() << ">(" << t_Foo.fooNum << "," << t_Foo.val << ")";
    os << ss.str();
    return os;
}

template <typename T>
struct Fooz
{
    int foozNum = 0;
    T val;

    Fooz(int someFooNum, T someVal) : foozNum(someFooNum), val(someVal) {}

    // Method 2: Define a non-templated operator<< overload. 
    friend ostream& operator<<(ostream& os, const Fooz& t_Fooz)
    {
        stringstream ss;
        ss << "Fooz<" << TypeName<T>::AsString() << ">(" << t_Fooz.foozNum << "," << t_Fooz.val << ")";
        os << ss.str();
        return os;
    }
};

void Example()
{

    Foo<int> f_int(1, -10);
    cout << f_int;
    cout << endl;

    Foo<string> f_str(2, "Alice");
    cout << f_str;
    cout << endl;

    Foo<Bar> f_bar(3, Bar(30));
    cout << f_bar;
    cout << endl;

    Fooz<Bar> fz_bar(4, Bar(40));
    cout << fz_bar;
    cout << endl;
}

int main()
{
    Example();
}


Foo<int>(1,-10)
Foo<string>(2,Alice)
Foo<Bar>(3,barNum:30)
Fooz<Bar>(4,barNum:40)


[1] https://stackoverflow.com/a/4661372/5972766
[2] https://stackoverflow.com/a/16562961/5972766
[3] https://stackoverflow.com/a/16562961/5972766
Last edited on
Topic archived. No new replies allowed.