BOOST_CHECK_EQUAL and operator<<

Hello, I am currently upgrading from boost-1.30.2 to boost-1.32.0 with MSVC 7.1 and I am running into the following issue: BOOST_CHECK_EQUAL requires the elements to be compared to provide an operator<< taking an std::ostream& as first parameter. I always had names lookups problems with that when the operator was provided by the test and not along with the element. Until now, I had the operator defined in "boost" namespace (that's ugly I know) in the test file. It breaks with 1.32.0 because the problematic function (print_log_value) moved deeped into test tools namespaces. Then I try to revert to a normal definition but it does not work either. For instance : //******************************* #include <boost/test/unit_test.hpp> using boost::unit_test::test_suite; namespace testns { struct Test { Test(std::string const& s_) : s(s_) {} bool operator==(Test const& t) const { return s==t.s; } std::string s; }; namespace { std::ostream& operator<<(std::ostream& o, Test const& t) { return o<<t.s; } void free_test_function() { Test t1("test1"), t2("test2"); BOOST_CHECK_EQUAL(t1, t2); } } //namespace } //namespace testns test_suite* init_unit_test_suite( int, char* [] ) { test_suite* test= BOOST_TEST_SUITE( "Unit test example 1" ); test->add( BOOST_TEST_CASE( &testns::free_test_function )); return test; } //*********************** Does not compile and gives me a: C2679: binary 'operator' : no operator defined which takes a right-hand operand of type 'const testns::Test' (or there is no acceptable conversion), the failure being issued by "void boost::test_tools::tt_detail::print_log_value<T>::operator ()(std::ostream &,const T &)" However, if I define operator<< as a public member of testns::Test it compiles. Same thing if I move it outside the anonymous namespace, with or without static linkage. Note these workaround do not work or cannot be applied if the type is declared by a typedef. In this case, I have to define again the operator in namespace boost.test_tools.tt_detail. Is it to be expected ? Is is an ADL issue ? Or something related to MSVC 7.1 ? What is the proper way to do that ? Patrick Mézard

Patrick M?zard wrote:
namespace testns {
struct Test { .... };
namespace {
std::ostream& operator<<(std::ostream& o, Test const& t) { return o<<t.s; }
...
Does not compile and gives me a: C2679: binary 'operator' : no operator defined which takes a right-hand operand of type 'const testns::Test' (or there is no acceptable conversion), the failure being issued by "void boost::test_tools::tt_detail::print_log_value<T>::operator ()(std::ostream &,const T &)"
However, if I define operator<< as a public member of testns::Test it compiles. Same thing if I move it outside the anonymous namespace, with or without static linkage.
I think defining operator<< in the same namespace as the class 'Test' is the only way. Otherwise, it won't be found by ADL and ADL is the only way to boost.test to find your operator<<.
Note these workaround do not work or cannot be applied if the type is declared by a typedef. In this case, I have to define again the operator in namespace boost.test_tools.tt_detail.
If the type is declared by a typedef: typedef foo::C C2; you can define the operator<< in the 'foo' namespace, and that should work. - Volodya

Vladimir Prus <ghost <at> cs.msu.su> writes:
Patrick M?zard wrote:
namespace testns {
struct Test { .... };
namespace {
std::ostream& operator<<(std::ostream& o, Test const& t) { return o<<t.s; }
...
Does not compile and gives me a: C2679: binary 'operator' : no operator defined which takes a right-hand operand of type 'const testns::Test' (or there is no acceptable conversion), the failure being issued by "void boost::test_tools::tt_detail::print_log_value<T>::operator ()(std::ostream &,const T &)"
However, if I define operator<< as a public member of testns::Test it compiles. Same thing if I move it outside the anonymous namespace, with or without static linkage.
I think defining operator<< in the same namespace as the class 'Test' is the only way. Otherwise, it won't be found by ADL and ADL is the only way to boost.test to find your operator<<.
Thank you for your answer. I did not know that ADL treats normal and anonymous namespaces differently (and cannot find anything related in google.groups or other mailing lists). However, it does not work with typedefed types. Here is a new example : //********************* #include <boost/test/unit_test.hpp> using boost::unit_test::test_suite; namespace testns { typedef std::pair<int, std::string> Test; std::ostream& operator<<(std::ostream& o, Test const& t) { return o<<t.first<<t.second; } namespace { void free_test_function() { Test t1(1, "test1"), t2(2, "test2"); BOOST_CHECK_EQUAL(t1, t2); } } //namespace } //namespace testns test_suite* init_unit_test_suite( int, char* [] ) { test_suite* test= BOOST_TEST_SUITE( "Unit test example 1" ); test->add( BOOST_TEST_CASE( &testns::free_test_function )); return test; } //********************* I get the same error (C2679) again with the same function instantiation "void boost::test_tools::tt_detail::print_log_value<T>::operator ()(std::ostream &,const T &)". Until now, to make it works I moved operator<< into "namespace boost { namespace test_tools { namespace tt_detail" but that's clearly not the way to go. Can you think of any workaround for this one (workaround or just a correct way to write that :-). I do not pretend to understand anything about ADL...). Patrick Mézard

Patrick M?zard wrote:
However, it does not work with typedefed types. Here is a new example :
//********************* #include <boost/test/unit_test.hpp> using boost::unit_test::test_suite;
namespace testns {
typedef std::pair<int, std::string> Test;
std::ostream& operator<<(std::ostream& o, Test const& t) { return o<<t.first<<t.second; }
Try writing: namespace std { std::ostream& operator<<(std::ostream& o, std::pair<int, std::string> const& t) { return o<<t.first<<t.second; } } I think namespace std is the only place that will be searched for operator<< (except for boost:: and for boost::test_tools and boost::test_tools::tt_detail). - Volodya

Vladimir Prus <ghost <at> cs.msu.su> writes:
I think namespace std is the only place that will be searched for operator<< (except for boost:: and for boost::test_tools and boost::test_tools::tt_detail).
Good pick, it works. I would like to know if the original version which defines the operator<< in testns:: is legal or not, but at least your solution will save me a lot of typing. Thank you very much. Patrick Mézard

Vladimir Prus wrote:
Patrick M?zard wrote:
[...]
I think defining operator<< in the same namespace as the class 'Test' is the only way. Otherwise, it won't be found by ADL and ADL is the only way to boost.test to find your operator<<.
Note these workaround do not work or cannot be applied if the type is declared by a typedef. In this case, I have to define again the operator in namespace boost.test_tools.tt_detail.
If the type is declared by a typedef:
typedef foo::C C2;
you can define the operator<< in the 'foo' namespace, and that should work.
That is very unfortunate. Is there no way to have the operator<< lookup occur in the scope of the call to BOOST_CHECK_EQUAL, by moving the wrap_stringstream logic and the call to operator<< into the macro itself ? Cheers, Ian

If the type is declared by a typedef:
typedef foo::C C2;
you can define the operator<< in the 'foo' namespace, and that should work.
That is very unfortunate. Is there no way to have the operator<< lookup occur in the scope of the call to BOOST_CHECK_EQUAL, by moving the wrap_stringstream logic and the call to operator<< into the macro itself ?
No. there are several reasons why it couldn't be done: 1. Printing log value in the scope of call would cause multiple value evaluation. Imagine what if I write BOOST_CHECK_EQUAL( i++, 2 ); 2. I need extra level of indirection to be able to prevent value from being printed (BOOST_TEST_DONT_PRINT_LOG_VALUE(type)) 3. I need extra level of indirection to be able to do custom printing for some types You have several choices 1. You could define operator<< in namespace foo 2. You could use BOOST_TEST_DONT_PRINT_LOG_VALUE(your type) on global scope to prevent values of this type being printed at all 3. You could write struct C2 : foo::C {}; And unless you use non-default constructor it will work. Gennadiy

Gennadiy Rozental <gennadiy.rozental <at> thomson.com> writes:
You have several choices
1. You could define operator<< in namespace foo
It does not work because of the typedef (see below). Please see my first answer to Vladimir. It works if it is defined in namespace std:: however.
2. You could use BOOST_TEST_DONT_PRINT_LOG_VALUE(your type) on global scope to prevent values of this type being printed at all
Okay, but since I am the one writing the tests, I would simply use BOOST_CHECK().
3. You could write struct C2 : foo::C {}; And unless you use non-default constructor it will work.
Maybe, but it seems much more work just to use BOOST_CHECK_EQUAL(). I could understand the anonymous namespace and typedefs have issues with ADL and there are workaround to be found. For those interested with ADL rules, I finally got an old post from llewely on comp.lang.c+.moderated on a similar problem stating that (look for "Re: stream iterators and operator<< " in google.groups): <quote> A typedef name is not a type. The occurance of a typedef name in a scope does not add that scope to consideration in argument-dependent lookup. [3.4.2/2] Unqualified lookup does not occur because inside the definition of the function called by '*it = *v.begin()', operator<< is used for a function call. Instead, argument-dependent lookup is used. [3.4.2/1] Argument dependent lookup will lookup names in the classes and namespaces associated with the argument types. pair<int,int> is a template-id, and defined at namespace scope in namespace std, with arguments int,int, which have no associated namespaces or classes, so the only associated namespace is std. [3.4.2/2, bullet 8] I believe both compilers are conforming in this respect. Some notes: If you move the operator<< into namespace std, it will be found during argument-dependent lookup (I tested with gcc 3.2.1), except for the caveat that one is not allowed to add overloads to namespace std. If you change 'typedef pair<int,int> Type' to 'typedef pair<int,Foo>', where Foo is in the global namespace, the operator<< will also be found. </quote> Personnaly, I would stick with declaring the operator<< in std:: for the moment. That's ugly but that's just tests after all. Maybe you could just add a documentation note on this topic. Thank you for your answer. Patrick Mézard.

namespace testns {
struct Test { Test(std::string const& s_) : s(s_) {}
bool operator==(Test const& t) const { return s==t.s; }
std::string s; };
namespace {
std::ostream& operator<<(std::ostream& o, Test const& t) { return o<<t.s; }
void free_test_function() { Test t1("test1"), t2("test2"); BOOST_CHECK_EQUAL(t1, t2); }
} //namespace
} //namespace testns
Why do you need unnamed namespace? Remove it and it will all work. Gennadiy

Gennadiy Rozental <gennadiy.rozental <at> thomson.com> writes:
Why do you need unnamed namespace? Remove it and it will all work.
The type being tested belongs to a library. I do not need the operator<< at the library level, only in the test for BOOST_CHECK_EQUAL support. So I define the operator at in the test scope, in an anonymous namespace because there is no need to export the symbol. That's good practice for me, maybe I am wrong ? Patrick Mézard
participants (4)
-
Gennadiy Rozental
-
Ian McCulloch
-
Patrick Mézard
-
Vladimir Prus