Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

What am I missing in this template toy example?

34 views
Skip to first unread message

Markus Saers

unread,
May 15, 2009, 4:47:02 PM5/15/09
to
Hello,

I've got a problem with a nested template class using standard output
operators (<<). As far as I can tell this should work, but the
compiler (GCC 4.3.2) disagrees. I've reduced it to a "canonical" case
and added the error messages as comments. Please advice.

#include<iostream>
using std::cout; using std::endl; using std::ostream;

template<typename T> class A;
template<typename T> ostream& operator<<(ostream&, const A<T>&);

template<typename T> class A {
public:
class B;
friend ostream& operator<< <>(ostream&, const A<T>&);
};

template<typename T> ostream& operator<<(ostream&, const typename
A<T>::B&);

template<typename T> class A<T>::B {
public:
friend ostream& operator<< <>(ostream&, const B&); //error: template-
id 'operator<< <>' for 'std::ostream& operator<<(std::ostream&, const
A<int>::B&)' does not match any template declaration
};

template<typename T> ostream& operator<<(ostream& os, const A<T>& a) {
cout << "a" << endl;
}

template<typename T> ostream& operator<<(ostream& os, const typename
A<T>::B& b) {
cout << "b" << endl;
}

int main(int argc, char** argv) {
A<int> a;
A<int>::B b;
cout << a;
cout << b; //error: no match for 'operator<<' in 'std::cout << b'
cout << endl;
}


Best regards
Markus

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Paul Bibbings

unread,
May 16, 2009, 5:34:48 PM5/16/09
to
Not being an expert myself on templates in c++ I was nevertheless
intrigued by your post as it seemed to me that I might have expected
it to compile also. However, having experimented with your code using
a number of compilers - gcc 3.4.4, MSVC++ 9.0, and Comeau (online) -
I have at least managed to move it toward something that will compile
and produce the expected output.

In terms of your two marked errors, it seems that the first is
removed through being explicit about the template parameter in your
declaration of the overloaded insertion operator as a friend of the
nested class B. That is, where you have:

<snip>


template<typename T> class A<T>::B {
public:
friend ostream& operator<< <>(ostream&, const B&); //error:

};
</snip>

use:
...
friend ostream& operator<< <T>(ostream&, const B&);
...

Still, this doesn't solve the second error, and in truth I would
suggest that it is not possible (or I have not found out how) to
define the operator outside of the in-class declaration and have the
simple usage:

<snip>
cout << b;
</snip>

compile. That the following explicit use of the operator *does*
compile, that is:

operator<< <int>(cout, b);

suggests that the problem here stems from the fact that, as B itself
is not a template, the simpler syntax does not permit the template
parameter on A to be deduced.

Thus, the following code at least works in the above sense. (Note:
I have tidied up the definitions of the overloaded insertion
operators - providing the missing return values, for one - and
have adapted the classes so that the friend operators actually
make use of otherwise private data. The reason for the last is that,
using your definitions where there is no such access, it is actually
possible to make other modifications and get it to build with only
the illusion that the called operator is defining the friend
declaration in B.)

#include<iostream>

using std::cout;
using std::endl;
using std::ostream;

//================================================================


template<typename T> class A;

//================================================================


template<typename T>
ostream& operator<<(ostream&, const A<T>&);

//================================================================
template<typename T> class A {
char a;
public:
A(): a('a') { }


class B;
friend ostream& operator<< <>(ostream&, const A<T>&);
};

//================================================================


template<typename T>
ostream& operator<<(ostream&, const typename A<T>::B&);

//================================================================


template<typename T> class A<T>::B {

char b;
public:
B(): b('b') { }
friend ostream& operator<< <T> (ostream&, const B&); // #1
};
//================================================================


template<typename T>
ostream& operator<<(ostream& os, const A<T>& a) {

return os << a.a;
}
//================================================================


template<typename T>
ostream& operator<<(ostream& os, const typename A<T>::B& b) {

return os << b.b;
}
//================================================================
template ostream& operator<< <int>(ostream&, const A<int>::B&);
//================================================================


int main(int argc, char** argv) {
A<int> a;
A<int>::B b;

cout << a << endl;
//cout << b << endl; // error #2 remains here
operator<< <int>(cout, b);
cout << endl;
}
//================================================================

Regards

Paul Bibbings

Dragan Milenkovic

unread,
May 17, 2009, 3:30:26 AM5/17/09
to
Paul Bibbings wrote:
[with all doe respect - snip]

> //================================================================
> template<typename T>
> ostream& operator<<(ostream&, const typename A<T>::B&);
> //================================================================
> template<typename T> class A<T>::B {
> char b;
> public:
> B(): b('b') { }
> friend ostream& operator<< <T> (ostream&, const B&); // #1
> };

You can try it like this:

template <typename T>
ostream & my_own_print(ostream &, const typename A<T>::B &);

template <typename T>
class A<T>::B {
public:

friend ostream& my_own_print<T>(ostream&, const B &);

friend ostream& operator<<(ostream& s, const B & b) {
return my_own_print(s, b);
}
};


BUT! Beware of the following bug:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34870

... I would appreciate if people review and comment on the bug.
It started as "rejects-valid", now it's "accepts-invalid".

--
Dragan

litb

unread,
May 17, 2009, 3:30:46 AM5/17/09
to

Yesterday, some guy came into our IRC channel asking that same
question, apparently because he also was inspired by that Problem and
looking for a solution. It's not too difficult to see the reason of
the failure once you read the explanation. The offending guy is this
template declaration:

template<typename T> ostream& operator<<(ostream& os, const typename


A<T>::B& b) {
cout << "b" << endl;
}

For the second parameter, it constructs a nondeduced context. Such
contexts can't be used to deduce a template argument. To see why that
is, consider:

struct A { };
template<typename> struct B { typedef T type; }
template<typename T> void f(typename B<T>::type t) { ... }

To deduce "T", the compiler will need to compare the parameter type
against the type of the function call argument. At that time, the
compiler doesn't know that "B<T>::type" is identical to "T": After
all, B<T> can be explicitly specialized and for that specialization,
"::type" can be defined another way. So it also can't know that T is
tried to match the function call argument type.

Another example for a non-deduced context is this one:

template<int A> void f(char (*f)[A + 1]) { ... }

It won't deduce A to 1 if you pass a pointer to an array of size 2.
Somewhere, the crazy complexity of C++ has to have an end.

To this end, you can put the operator function as a friend definition.
Since it's already going to be a friend anyway, this only can be of
your favor:

template<typename T> class A<T>::B {
public:

friend ostream& operator<<(ostream&, const B&) {
...
}
};

Now, whenever A<T>::B appears in a call to op<<, that declaration will
be visible to the compiler since A<T>::B will be an associated class
in these calls (ADL). Like in all these cases:

cout << A<int>::B();
cout << make_pair(42, A<int>::B());

However, if there is no link from an argument to A<int>::B, then there
is no way for the compiler to see that declaration of op<<. Thus, if
A<int>::B has a converting ctor taking an FooBarBaz, the following
would still not invoke that op<<, since A<int>::B is not an associated
class.

cout << FooBarBaz();

Hope this has helped you!

Paul Bibbings

unread,
May 17, 2009, 11:38:01 AM5/17/09
to
Dragan, I would like to clarify a couple of points about your
suggested solution (below), if I may.

Dragan Milenkovic wrote:
> template <typename T>
> ostream & my_own_print(ostream &, const typename A<T>::B &);
>
> template <typename T>
> class A<T>::B {
> public:
> friend ostream& my_own_print<T>(ostream&, const B &);
>
> friend ostream& operator<<(ostream& s, const B & b) {
> return my_own_print(s, b);
> }
> };

Firstly, to my understanding, I find that the call to my_own_print
in the definition of friend op<< requires specification of the
template argument in order to effect instantation. Thus:

template<typename T>
class A<T>::B {
public:

// ...


friend ostream& operator<< (ostream& s, const B& b) {

return my_own_print<T>(s, b); // specify temp arg here
}
};

Secondly, can you clarify how your proposed solution improves on
simply inlining (if that is the right word) the original op<<
without the re-direction through my_own_print? I.e., as:

template<typename T>
class A<T>::B {
public:

// ...
friend ostream& operator<< (ostream& os, const B& b) {
return os << 'b';
}
};

which, equally, solves the OP's original problem, permitting use
of:

A<int>::B b
cout << b;

Many thanks

Paul Bibbings

--

litb

unread,
May 17, 2009, 11:37:34 AM5/17/09
to

The Standard says that the original example should be rejected (with-
and without a previous instantiation of the class-template). The
reason is that the name of the friend function template is not visible
at the point of the call in "check". Therefor, the unqualified-id
that's required by ADL can't form a template-id, because for that, the
template-name appearing before the arguments for that template must be
known as a template before. But as name-lookup can't find that
template (it's not visible), that won't happen (14.2/2). The note at
14.8.1/6 further clears up the matter.

Your other example, where the friend function was actually a non-
template and the call to it was an ordinary function call is fine and
valid C++.


--

Dragan Milenkovic

unread,
May 17, 2009, 11:37:41 AM5/17/09
to
Dragan Milenkovic wrote:
[snip]

> template <typename T>
> class A<T>::B {
> public:
> friend ostream& my_own_print<T>(ostream&, const B &);
>
> friend ostream& operator<<(ostream& s, const B & b) {
> return my_own_print(s, b);
> }
> };

... but I left out <T>...

return my_own_print<T>(s, b);

--

Dragan Milenkovic

unread,
May 18, 2009, 10:43:55 AM5/18/09
to
litb wrote:
> On 17 Mai, 09:30, Dragan Milenkovic <dra...@plusplus.rs> wrote:
[snip]

>> BUT! Beware of the following bug:
>>
>> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34870
>>
>> ... I would appreciate if people review and comment on the bug.
>> It started as "rejects-valid", now it's "accepts-invalid".
>>
>
> The Standard says that the original example should be rejected (with-
> and without a previous instantiation of the class-template). The
> reason is that the name of the friend function template is not visible
> at the point of the call in "check". Therefor, the unqualified-id
> that's required by ADL can't form a template-id, because for that, the
> template-name appearing before the arguments for that template must be
> known as a template before. But as name-lookup can't find that
> template (it's not visible), that won't happen (14.2/2). The note at
> 14.8.1/6 further clears up the matter.
>
> Your other example, where the friend function was actually a non-
> template and the call to it was an ordinary function call is fine and
> valid C++.

Thank you very much for looking.

Yes, 14.8.1/6 clears up the matter. Although I can not find any
reasonable explanation.


namespace A {
struct B { };
template<int X> void f(B);
}
namespace C {
template<class T> void f(T t);
}
void g(A::B b) {

f<3>(b); // ill-formed: not a function call

A::f<3>(b); // well-formed

C::f<3>(b); // ill-formed; argument dependent lookup
// applies only to unqualified names
using C::f;

f<3>(b); // well-formed because C::f is visible; then
// A::f is found by argument dependent lookup
}


What does C::f have to do with A::f ?!? I don't find any logical
reason why visibility, or anything else, of A::f can be influenced by
something that is in another namespace and completely unrelated.

What's worse, C::f<3> doesn't make sense since 3 is not a class.

I still believe that the way I see (or should I say imagine) things
is the way it should be. It's there in examples in the mentioned
bug report.

Am I right?

--
Dragan

Markus Saers

unread,
May 18, 2009, 10:43:37 AM5/18/09
to
Thank you all for the replies! I see why it isn't working now (guess
you really do learn something every day). The reason I do not want to
inline the operator<<-method is the same as my reasons for not
inlining other methods: readability of the interface and the risk of
code bloating (I'll be bigger than just "os << 'b';"). I'll probably
implement the one line call to another (private) my_print-method.

Thanks again
Markus

--

Dragan Milenkovic

unread,
May 18, 2009, 10:43:52 AM5/18/09
to
Paul Bibbings wrote:
> Dragan, I would like to clarify a couple of points about your
> suggested solution (below), if I may.
[snip]

> Firstly, to my understanding, I find that the call to my_own_print
> in the definition of friend op<< requires specification of the
> template argument in order to effect instantation. Thus:

Yes. I have corrected myself, but too late...

> Secondly, can you clarify how your proposed solution improves on
> simply inlining (if that is the right word) the original op<<
> without the re-direction through my_own_print? I.e., as:

It just in case you want to move implementation outside of the class.
I thought this was what OP tried to do, since I'm sure he knows
about inlining friends. If it's really a simple "return os << 'b'",
then ignore my proposal.

--
Dragan

litb

unread,
May 18, 2009, 4:35:23 PM5/18/09
to
On 18 Mai, 16:43, Dragan Milenkovic <dra...@plusplus.rs> wrote:
> litb wrote:
> > ... The note at

I changed the example somewhat, to explain matters further. I think
it's clearer now why making visible C::f changes behavior all the way:

#include <iostream>

namespace A {
struct B { };

void operator>(bool,B) {
std::cout << "op>" << std::endl;
}

template<int X> void f(B) {
std::cout << "f(B)" << std::endl;
}
}

namespace C {
template<class T> void f(T t);
}

int f;

void g(A::B b) {
f<3>(b);

A::f<3>(b); // well-formed

using C::f;
f<3>(b);
}

int main() { A::B b; g(b); }

The first call to f<0>(b) now is not ill-formed anymore, because it
now finds a suitable typed "f" at ::f, from which it can build a valid
boolean expression up like this: ((f<0)>b). We overloaded "op>
(bool,A::B)" and thus the example will output

op>
f(B)
f(B)

But since we made C::f visible, the compiler considers f<0> now as a
primary expression naming a template-id (it has not yet checked
whether the template-argument is compatible with the parameter. That's
done later, of course). *Now*, it can start doing ADL, since the pre-
condition that the post-expression in the function call is a
unqualified-id is satisfied, and will find "f" to be also in A::. Hope
it makes sense!


--

karl.mi...@gmail.com

unread,
May 19, 2009, 8:38:39 AM5/19/09
to
On May 19, 6:35 am, litb <Schaub-Johan...@web.de> wrote:
>
> I changed the example somewhat, to explain matters further. I think
> it's clearer now why making visible C::f changes behavior all the way:
>
> #include <iostream>
>
> namespace A {
> struct B { };
>
> void operator>(bool,B) {
> std::cout << "op>" << std::endl;
> }
>
> template<int X> void f(B) {
> std::cout << "f(B)" << std::endl;
> }
>
> }
>
> namespace C {
> template<class T> void f(T t);
>
> }
>
> int f;
>
> void g(A::B b) {
> f<3>(b);
> A::f<3>(b); // well-formed
>
> using C::f;
> f<3>(b); // g++ says error here

>
> }
>
> int main() { A::B b; g(b); }
>

This compiles with Comeau (4.3.10.1) but not with g++ (4.3.3) which
tells me

$ g++ -o test test.cpp
test.cpp: In function 'void g(A::B)':
test.cpp:26: error: no matching function for call to 'f(A::B&)'

Cheers
Karl

Paul Bibbings

unread,
May 19, 2009, 8:39:02 AM5/19/09
to
On May 18, 3:43 pm, Markus Saers <masa...@gmail.com> wrote:
> Thank you all for the replies! I see why it isn't working now (guess
> you really do learn something every day). The reason I do not want to
> inline the operator<<-method is the same as my reasons for not
> inlining other methods: readability of the interface and the risk of
> code bloating (I'll be bigger than just "os << 'b';"). I'll probably
> implement the one line call to another (private) my_print-method.

If you are to choose an implementation that redirects through a
helper function then, as you say, having this as a private member
method seems like the right way to go. In this way you avoid
cluttering the public interface of your module with methods that are
only intended to be called indirectly. Something like:

template<typename T> class A<T>::B {

ostream& print(ostream&) const;
public:
friend ostream& operator<< (ostream& os, const B& b) {
return b.print(os);
}
};

template<typename T>
ostream& A<T>::B::print(ostream &os) const {
return os << 'b';

Dragan Milenkovic

unread,
May 21, 2009, 9:48:11 AM5/21/09
to
litb wrote:
[snip]

> The first call to f<0>(b) now is not ill-formed anymore, because it
> now finds a suitable typed "f" at ::f, from which it can build a valid
> boolean expression up like this: ((f<0)>b). We overloaded "op>
> (bool,A::B)" and thus the example will output
>
> op>
> f(B)
> f(B)
>
> But since we made C::f visible, the compiler considers f<0> now as a
> primary expression naming a template-id (it has not yet checked
> whether the template-argument is compatible with the parameter. That's
> done later, of course). *Now*, it can start doing ADL, since the pre-
> condition that the post-expression in the function call is a
> unqualified-id is satisfied, and will find "f" to be also in A::. Hope
> it makes sense!

If I understood correctly, it's parser related? I'll ask in
a new thread since I find it logical that template function should
be found on it's on.

Thanks for your explanation.

--
Dragan

0 new messages