Interesting overload case

D

David Sachs

The following program illustrates an interesting effect of the way C++
resolves function overloading.

I have verified with a member of the C++ stardard committee that the output
shown is correct.

*********************************Program********************************

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

namespace test
{
enum test1 { testa=3, testb=4 };

std::eek:stream& operator<<(std::eek:stream& os, test::test1 t)
{
os << " **** TEST1 **** ";
return os;
}

enum test2 { testc = 5, testd = 7 };
}

std::eek:stream& operator<<(std::eek:stream& os, test::test2 t)
{
os << " **** TEST2 **** ";
return os;
}

test::test1 i(test::testa);
test::test2 j(test::testc);

namespace test
{
void testy()
{
cout << i << endl;
cout << j << endl;
}
}

int main()
{
cout << "Direct\n";
cout << i << endl;
cout << j << endl;
cout << "Indirect\n";
test::testy();
return 0;
}

********************************Output********************************

Direct
**** TEST1 ****
**** TEST2 ****
Indirect
**** TEST1 ****
5
 
D

Dave

David Sachs said:
The following program illustrates an interesting effect of the way C++
resolves function overloading.

I have verified with a member of the C++ stardard committee that the output
shown is correct.

*********************************Program********************************

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

namespace test
{
enum test1 { testa=3, testb=4 };

std::eek:stream& operator<<(std::eek:stream& os, test::test1 t)
{
os << " **** TEST1 **** ";
return os;
}

enum test2 { testc = 5, testd = 7 };
}

std::eek:stream& operator<<(std::eek:stream& os, test::test2 t)
{
os << " **** TEST2 **** ";
return os;
}

test::test1 i(test::testa);
test::test2 j(test::testc);

namespace test
{
void testy()
{
cout << i << endl;
cout << j << endl;
}
}

int main()
{
cout << "Direct\n";
cout << i << endl;
cout << j << endl;
cout << "Indirect\n";
test::testy();
return 0;
}

********************************Output********************************

Direct
**** TEST1 ****
**** TEST2 ****
Indirect
**** TEST1 ****
5

What is the cause of this?
 
V

Victor Bazarov

Dave said:
What is the cause of this?

The cause is that when resolving which operator<< to use for
test::test2 _inside_ 'testy' function, the compiler is _not_
considering the global namespace, and cannot find the output
operator defined just above 'i' and 'j' objects. It has to
resort to finding a suitable std::eek:stream::eek:perator<< along
with converting an enum value into 'int'.

At least that's how I remember it.

Victor
 
P

Patrick Kowalzick

Hello NG,

just to make some more mess I changed a little bit the code here:

// **** CODE CHANGES ****
namespace test
{
void testy()
{
operator<<(cout,i) << endl;
cout << j << endl; // **** TEST2 **** in bcc and "5" in MSVC and g++
// operator<<(cout,j) << endl;
// this one will not compile with MSVC 7.1, g++3.3.1
// but compiles with bcc 5.5.1: **** TEST2 ****
::eek:perator<<(cout,j) << endl; // **** TEST2 ****
// but this works how expected:
}
}

// **** CODE END ****

I used the direct call of the operator, and I think it is getting really
weird. Nicely enough the borland compiler does not agree with the others
(neither did it before the change). In fact I have promblems understanding
it :-(

1.) I my opinion the call

A << B;

should produce the same result like

operator<<(A,B);

If not, why and for what reason not? Is it still transparent then?

But:
MSVC7.1 and g++ will not compile

operator<<(cout,j) << endl; // neither: std::eek:perator<<(cout,j) << endl;

but both compile this line:

cout << j << endl;

which operator is called then?

2.) This one compiles and works fine

::eek:perator<<(cout,j) << endl;

does what it should do :). Nice


Regards,
Patrick
 
D

David Sachs

Patrick Kowalzick said:
Hello NG,

just to make some more mess I changed a little bit the code here:

// **** CODE CHANGES ****
namespace test
{
void testy()
{
operator<<(cout,i) << endl;
cout << j << endl; // **** TEST2 **** in bcc and "5" in MSVC and g++
// operator<<(cout,j) << endl;
// this one will not compile with MSVC 7.1, g++3.3.1
// but compiles with bcc 5.5.1: **** TEST2 ****
::eek:perator<<(cout,j) << endl; // **** TEST2 ****
// but this works how expected:
}
}

// **** CODE END ****

I used the direct call of the operator, and I think it is getting really
weird. Nicely enough the borland compiler does not agree with the others
(neither did it before the change). In fact I have promblems understanding
it :-(

1.) I my opinion the call

A << B;

should produce the same result like

operator<<(A,B);

If not, why and for what reason not? Is it still transparent then?

But:
MSVC7.1 and g++ will not compile

operator<<(cout,j) << endl; // neither: std::eek:perator<<(cout,j) << endl;

but both compile this line:

cout << j << endl;

which operator is called then?

2.) This one compiles and works fine

::eek:perator<<(cout,j) << endl;

does what it should do :). Nice


Regards,
Patrick
There actually is a subtle difference between

cout << j;

and

operator<<(cout,j);

when the enem-specific form of operator<< is unavailable.

The first form (cout << j) will cause integral promotion of j. The C++
standard explicitly states that the enum will be converted to an int if
feasible, This feature allows displaying the numeric value of an enum if
operator<< is not overloaded for it.

The second form (explicit function call) will use whatever conversions of
the enum are feasible. For some C++ compilers such as g++, this will cause a
compile time error, because the forms

operator<<(ostream&, char)
operator<<(ostream&, unsigned char)
operator<<(ostream&, signed char)

are equally good, and better than anything else. A compiler that stores enum
s as int s would not notice this problem.
 
A

Andrey Tarasevich

Dave said:
What is the cause of this?
...

Inside 'test::testy' the name lookup for 'operator <<' works as follows:

1) Ordinary unqualified name lookup for 'operator<<' begins in namespace
'test' and immediately finds 'test::eek:perator<<'.
2) Argument-dependent name lookup on the first argument finds a bunch of
'std::basic_ostream<>::eek:perator<<' functions.
3) Argument-dependent name lookup on the second argument also finds
'test::eek:perator<<'

Note that '::eek:perator<<' is not found by name lookup. In this situation
overload resolution has no choice but to go with one of 'operator <<'
member functions of 'std::basic_ostream<>', since 'test::eek:perator<<'
cannot be used with value of type 'test2'.

Inside 'main' the name lookup for 'operator <<' works as follows:

1) Ordinary unqualified name lookup for 'operator<<' begins in global
scope and finds '::eek:perator<<'.
2) Argument-dependent name lookup on the first argument finds a bunch of
'std::basic_ostream<>::eek:perator<<' functions.
3) Argument-dependent name lookup on the second argument finds
'test::eek:perator<<'

In this case overload resolution will choose '::eek:perator<<' as the best
match. That explains the behavior of the program.

Note, that if you remove the declaration of 'test::eek:perator<<', the
ordinary unqualified name lookup inside 'test::testy' will not stop
inside namespace 'test' and will continue into the global scope, where
it will find '::eek:perator<<'. In this case, both 'test::testy' and 'main'
will output identical results.
 
A

Andrey Tarasevich

Andrey said:
...
Inside 'test::testy' the name lookup for 'operator <<' works as follows:

1) Ordinary unqualified name lookup for 'operator<<' begins in namespace
'test' and immediately finds 'test::eek:perator<<'.
2) Argument-dependent name lookup on the first argument finds a bunch of
'std::basic_ostream<>::eek:perator<<' functions.
3) Argument-dependent name lookup on the second argument also finds
'test::eek:perator<<'

Note that '::eek:perator<<' is not found by name lookup. In this situation
overload resolution has no choice but to go with one of 'operator <<'
member functions of 'std::basic_ostream<>', since 'test::eek:perator<<'
cannot be used with value of type 'test2'.

Inside 'main' the name lookup for 'operator <<' works as follows:

1) Ordinary unqualified name lookup for 'operator<<' begins in global
scope and finds '::eek:perator<<'.
2) Argument-dependent name lookup on the first argument finds a bunch of
'std::basic_ostream<>::eek:perator<<' functions.
3) Argument-dependent name lookup on the second argument finds
'test::eek:perator<<'

In this case overload resolution will choose '::eek:perator<<' as the best
match. That explains the behavior of the program.

Note, that if you remove the declaration of 'test::eek:perator<<', the
ordinary unqualified name lookup inside 'test::testy' will not stop
inside namespace 'test' and will continue into the global scope, where
it will find '::eek:perator<<'. In this case, both 'test::testy' and 'main'
will output identical results.

It is probably worth adding that in short this behavior is explained by
the simple fact that form the point of view of function 'test::testy'
'operator<<' declared inside namespace 'test' _hides_ global
'operator<<'. The global 'operator<<' can be "unhidden" by a
using-declaration in namespace 'test'

namespace test
{
using ::eek:perator<<;

void testy()
{
cout << i << endl;
cout << j << endl;
}
}

In this case 'testy' will call the same 'operator <<' functions as
'main' does.
 
P

Patrick Kowalzick

Hello David,
There actually is a subtle difference between

cout << j;

and

operator<<(cout,j);

when the enem-specific form of operator<< is unavailable.

The first form (cout << j) will cause integral promotion of j. The C++
standard explicitly states that the enum will be converted to an int if
feasible, This feature allows displaying the numeric value of an enum if
operator<< is not overloaded for it.

The second form (explicit function call) will use whatever conversions of
the enum are feasible. For some C++ compilers such as g++, this will cause a
compile time error, because the forms

operator<<(ostream&, char)
operator<<(ostream&, unsigned char)
operator<<(ostream&, signed char)

are equally good, and better than anything else. A compiler that stores enum
s as int s would not notice this problem.

This I got and later this day I try to find it in the Standard. But I do not
like it. For me it seems not to be transparent if there is such a difference
between the first and the second form.

And doesnt't this mean, that your original program may produce

Direct
**** TEST1 ****
**** TEST2 ****
Indirect
**** TEST1 ****
5

what is obviously right, but may produce as well with another compiler

Direct
**** TEST1 ****
**** TEST2 ****
Indirect
**** TEST1 ****
**** TEST2 ****

what could be a correct result as well. It just dependes on the type used
for enum. This would explain the differnt behavior of g++ (and MSVC)
compared to bcc.
Is this correct like this?

Regard,
Patrick
 
A

Andrey Tarasevich

Patrick said:
And doesnt't this mean, that your original program may produce

Direct
**** TEST1 ****
**** TEST2 ****
Indirect
**** TEST1 ****
5

what is obviously right, but may produce as well with another compiler

Direct
**** TEST1 ****
**** TEST2 ****
Indirect
**** TEST1 ****
**** TEST2 ****

what could be a correct result as well. It just dependes on the type used
for enum. This would explain the differnt behavior of g++ (and MSVC)
compared to bcc.
Is this correct like this?
...

No, it doesn't depend on the type used for enum. The behavior of the
original program is explained by C++ name lookup rules. See my messages
in this thread for complete explanation.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top