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! ]
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
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
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!
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
--
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++.
--
... but I left out <T>...
return my_own_print<T>(s, b);
--
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
Thanks again
Markus
--
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
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!
--
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
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';
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