type conversion

F

frs

See example below: Why does the output of 'a' work and the output of
'b' fails to compile? Is there a way to write class 'something' so that
'b' converts correctly by default? (include iostream, string, use
namespace std)


template <typename T>
struct something {
T x;
operator T();
};

int
main(int, char**)
{
something<int> a;
something<string> b;

a.x = 4711;
b.x = "hello world!";

cout << a << endl; // compiles fine!
cout << b << endl; // operator<< ... no match, compile fails!
}


compiler = gcc 3.3.5

Thanks for any Help.

Frank
 
J

John Harrison

frs said:
See example below: Why does the output of 'a' work and the output of
'b' fails to compile? Is there a way to write class 'something' so that
'b' converts correctly by default? (include iostream, string, use
namespace std)


template <typename T>
struct something {
T x;
operator T();
};

int
main(int, char**)
{
something<int> a;
something<string> b;

a.x = 4711;
b.x = "hello world!";

cout << a << endl; // compiles fine!
cout << b << endl; // operator<< ... no match, compile fails!
}


compiler = gcc 3.3.5

Thanks for any Help.

Frank

It's probably got something to do with the fact that string itself is a
template.

For instance the following does compile

template <typename T>
struct something {
T x;
operator T();
};

struct S
{
};

ostream& operator<<(ostream&, const S&);

int main(int, char**)
{
something<int> a;
something<S> b;

a.x = 4711;
b.x = S();

cout << a << endl; // compiles fine!
cout << b << endl; // compiles fine!
}

I don't know the complete answer to your question but really my advice
would be to stay away from this stuff. I think even BS said stay away
from the obscure corners of the language.

John
 
B

benben

John Harrison said:
It's probably got something to do with the fact that string itself is a
template.

For instance the following does compile

template <typename T>
struct something {
T x;
operator T();
};

struct S
{
};

ostream& operator<<(ostream&, const S&);

int main(int, char**)
{
something<int> a;
something<S> b;

a.x = 4711;
b.x = S();

cout << a << endl; // compiles fine!
cout << b << endl; // compiles fine!
}

I don't know the complete answer to your question but really my advice
would be to stay away from this stuff. I think even BS said stay away from
the obscure corners of the language.

John

Had that been the case then the line

something<string> b;

would have failed to compile.

One possibility is that the string used wasn't std::string and doesn't
provide output operation with streams.

Ben
 
B

benben

hmm, it really didn't compile with std::string...I can't figure out what's
the cause...

Anyway the following will do what the OP might want to do:

template <typename T>
struct something
{
T x;
};

template <typename OStreamT, typename T>
OStreamT& operator << (
OStreamT& os, const something<T>& o)
{
return os << o.x;
}

int main(void)
{
something<int> a;
something<std::string> b;

a.x = 4711;
b.x = "hello world!";

std::cout << a << std::endl;
std::cout << b << std::endl;
}

ben
 
J

John Harrison

benben said:
Had that been the case then the line

something<string> b;

would have failed to compile.

Huh? That's a completely different situation.

John
 
J

John Harrison

benben said:
hmm, it really didn't compile with std::string...I can't figure out what's
the cause...

I don't know the reason either, what I do know is that the rules
governing this kind of thing are extremely complex (try reading the C++
standard) and often have non-intuitive effects. That is why the advice
is to stay away from obscure corners of the language.

John
 
B

benben

I don't know the reason either, what I do know is that the rules governing
this kind of thing are extremely complex (try reading the C++ standard)
and often have non-intuitive effects. That is why the advice is to stay
away from obscure corners of the language.

John

You are probably right on that the problem may originate from that fact that
string in itself is an instantiation of a class template because of the need
to instantiate a copy of the overloaded operator << function. I played
around with the code...like the one below:

#include <iostream>
#include <string>

template <typename T>
struct something
{
T x;
operator T(){return x;}
};

template <typename T>
class some_curious_stuff{};

template <typename T, typename OS>
OS& operator << (OS& os, const some_curious_stuff<T>&)
{
return os << "curious stuff";
}

// explicitly instantiate a copy of operator <<
template std::eek:stream& operator <<(std::eek:stream&, const
some_curious_stuff<int>&);

/*
Commented out operator overloading below:
if I uncomment the code below and comment out the explicit
instantiation the code gets compiled...
*/
//
//std::eek:stream& operator << (std::eek:stream& os, const
some_curious_stuff<int>&)
//{
// return os << "curious int stuff";
//}

int main(void)
{
some_curious_stuff<int> a;
std::cout << a;

something<some_curious_stuff<int> > b;
std::cout << b; // Still ERROR
}


My impression is the explicitly instantiated operator << is not
overloaded....somehow...

Ben
 
A

Alipha

frs said:
See example below: Why does the output of 'a' work and the output of
'b' fails to compile? Is there a way to write class 'something' so that
'b' converts correctly by default? (include iostream, string, use
namespace std)


template <typename T>
struct something {
T x;
operator T();
};

int
main(int, char**)
{
something<int> a;
something<string> b;

a.x = 4711;
b.x = "hello world!";

cout << a << endl; // compiles fine!
cout << b << endl; // operator<< ... no match, compile fails!
}


compiler = gcc 3.3.5

Thanks for any Help.

Frank

user-defined conversion operators are not considered when the compiler
tries to deduce template arguments for the operator<< call. you could:

(1) provide operator<< and operator>> for your something class.

(2) derive from T instead of having a T member:

template<typename T>
struct something : T {};

then provide template specializations for primitive types, that perhaps
use composition like your original solution. This is definitely the
more questionable solution.
 
A

andy

I think the problem is due to the fact that your are using templates
and overloading at the same time when you insert b into cout.

When you insert 'a' into cout you are calling a member function of cout
(although it is a template all the instantation can happen based on the
class which is obviously basic_ostream<char>).

template <typename T>
basic_ostream<T>&
baisc_ostream<T>::eek:perator<<(int value);

All the compiler has to do here is realize that an implict cast to int
is possible and that one of the member functions of baisc_ostream takes
int.

When you try to insert 'b' you are calling a template function:

template < typename charT >
basic_ostream< charT >&
operator << ( basic_ostream < charT >& is,
basic_string< charT > )

In order for this function to be called two things would need to happen
1. the compiler would need to realize that an implicit conversion to
basic_string<char> is possible.
2. The template would need to be instantated for basic_string<char>

This doesn't happen because template resolution happens before the
compiler starts looking for implicit casts. This means that the
function for inserting basic_string<T> is never considered and you get
an error message saying an appropiate operator << cannot be found.

I hope this makes sense (and is correct).
 
B

benben

user-defined conversion operators are not considered when the compiler
tries to deduce template arguments for the operator<< call. you could:

(1) provide operator<< and operator>> for your something class.

(2) derive from T instead of having a T member:

template<typename T>
struct something : T {};

then provide template specializations for primitive types, that perhaps
use composition like your original solution. This is definitely the
more questionable solution.

Then how do you explain that cout << a works where a is of type
something<int>?

Ben
 
B

benben

andy said:
I think the problem is due to the fact that your are using templates
and overloading at the same time when you insert b into cout.

When you insert 'a' into cout you are calling a member function of cout
(although it is a template all the instantation can happen based on the
class which is obviously basic_ostream<char>).

template <typename T>
basic_ostream<T>&
baisc_ostream<T>::eek:perator<<(int value);

All the compiler has to do here is realize that an implict cast to int
is possible and that one of the member functions of baisc_ostream takes
int.

When you try to insert 'b' you are calling a template function:

template < typename charT >
basic_ostream< charT >&
operator << ( basic_ostream < charT >& is,
basic_string< charT > )

In order for this function to be called two things would need to happen
1. the compiler would need to realize that an implicit conversion to
basic_string<char> is possible.
2. The template would need to be instantated for basic_string<char>

This doesn't happen because template resolution happens before the
compiler starts looking for implicit casts. This means that the
function for inserting basic_string<T> is never considered and you get
an error message saying an appropiate operator << cannot be found.

I hope this makes sense (and is correct).

This certainly makes sense! Thanks Andy!

Ben
 
G

Greg

andy said:
I think the problem is due to the fact that your are using templates
and overloading at the same time when you insert b into cout.

When you insert 'a' into cout you are calling a member function of cout
(although it is a template all the instantation can happen based on the
class which is obviously basic_ostream<char>).

template <typename T>
basic_ostream<T>&
baisc_ostream<T>::eek:perator<<(int value);

All the compiler has to do here is realize that an implict cast to int
is possible and that one of the member functions of baisc_ostream takes
int.

When you try to insert 'b' you are calling a template function:

template < typename charT >
basic_ostream< charT >&
operator << ( basic_ostream < charT >& is,
basic_string< charT > )

In order for this function to be called two things would need to happen
1. the compiler would need to realize that an implicit conversion to
basic_string<char> is possible.
2. The template would need to be instantated for basic_string<char>

This doesn't happen because template resolution happens before the
compiler starts looking for implicit casts. This means that the
function for inserting basic_string<T> is never considered and you get
an error message saying an appropiate operator << cannot be found.

I hope this makes sense (and is correct).

No, the templates are irrelevant. The actual explanation for why a
something<int> object works with ostream's << operator while a
something<std::string> does not, is far more simple.

The reason is that the compiler will apply at most one user-defined
conversion when looking for a function declaration to match a function
call in the source code. Passing a something<std::string> object to
ostream's << operator requires at least two conversions: the first to
convert the something<std::string> object to a std::string and a second
to convert the std::string to some other type for which ostream has
defined the << operator.

Were ostream to define the << operator for a std::string type, then
only one user-defined conversion would be needed in this case. And in
fact ostream defines the << operator for the type int - which explains
why you can call it with a something<int> object.

Greg
 
J

John Harrison

Greg said:
No, the templates are irrelevant. The actual explanation for why a
something<int> object works with ostream's << operator while a
something<std::string> does not, is far more simple.

The reason is that the compiler will apply at most one user-defined
conversion when looking for a function declaration to match a function
call in the source code. Passing a something<std::string> object to
ostream's << operator requires at least two conversions: the first to
convert the something<std::string> object to a std::string and a second
to convert the std::string to some other type for which ostream has
defined the << operator.

Were ostream to define the << operator for a std::string type, then
only one user-defined conversion would be needed in this case. And in
fact ostream defines the << operator for the type int - which explains
why you can call it with a something<int> object.

Greg

Sorry but there is an overload of the << operator defined for ostream
and string.

john
 
G

Greg

Sorry but there is an overload of the << operator defined for ostream
and string.

john

Let's see whether such a operator exists in the Standard Library. Below
is the list of operator << overloads that a conforming C++ Standard
Library must implement for a std::eek:stream:

// as member functions:
ostream& operator<< (bool& val );
ostream& operator<< (short& val );
ostream& operator<< (unsigned short& val );
ostream& operator<< (int& val );
ostream& operator<< (unsigned int& val );
ostream& operator<< (long& val );
ostream& operator<< (unsigned long& val );
ostream& operator<< (float& val );
ostream& operator<< (double& val );
ostream& operator<< (long double& val );
ostream& operator<< (void*& val );
ostream& operator<< (streambuf& sb );
ostream& operator<< (ostream& ( *pf )(ostream&));
ostream& operator<< (ios& ( *pf )(ios&));
ostream& operator<< (ios_base& ( *pf )(ios_base&));

// as external functions:
ostream& operator<< (ostream& os, char ch );
ostream& operator<< (ostream& os, signed char ch );
ostream& operator<< (ostream& os, unsigned char ch );
ostream& operator<< (ostream& os, const char* str );
ostream& operator<< (ostream& os, const signed char* str );
ostream& operator<< (ostream& os, const unsigned char* str );

Source:
http://www.cplusplus.com/ref/iostream/ostream/operatorltlt.html.

As I explained in my earlier post, the fact that the Standard Library
does not define such an operator means that the compiler would have to
perform two user-defined conversions to find a matching function to
compile the code in question - one more than the Standard allows.

Of course, there is nothing stopping anyone from writing the "missing"
overloaded operator << to verify this fact:

std::eek:stream&
operator<<( std::eek:stream& lhs, const std::string& rhs )
{
lhs.write( rhs.c_str(), rhs.length());

return lhs;
}

As expected, adding this function to the program does fix the compiler
error because the compiler now needs to perform only one user-defined
conversion to find a matching function.

Greg
 
J

John Harrison

Greg said:
Let's see whether such a operator exists in the Standard Library. Below
is the list of operator << overloads that a conforming C++ Standard
Library must implement for a std::eek:stream:

// as member functions:
ostream& operator<< (bool& val );
ostream& operator<< (short& val );
ostream& operator<< (unsigned short& val );
ostream& operator<< (int& val );
ostream& operator<< (unsigned int& val );
ostream& operator<< (long& val );
ostream& operator<< (unsigned long& val );
ostream& operator<< (float& val );
ostream& operator<< (double& val );
ostream& operator<< (long double& val );
ostream& operator<< (void*& val );
ostream& operator<< (streambuf& sb );
ostream& operator<< (ostream& ( *pf )(ostream&));
ostream& operator<< (ios& ( *pf )(ios&));
ostream& operator<< (ios_base& ( *pf )(ios_base&));

// as external functions:
ostream& operator<< (ostream& os, char ch );
ostream& operator<< (ostream& os, signed char ch );
ostream& operator<< (ostream& os, unsigned char ch );
ostream& operator<< (ostream& os, const char* str );
ostream& operator<< (ostream& os, const signed char* str );
ostream& operator<< (ostream& os, const unsigned char* str );

Source:
http://www.cplusplus.com/ref/iostream/ostream/operatorltlt.html.

As I explained in my earlier post, the fact that the Standard Library
does not define such an operator means that the compiler would have to
perform two user-defined conversions to find a matching function to
compile the code in question - one more than the Standard allows.

Of course, there is nothing stopping anyone from writing the "missing"
overloaded operator << to verify this fact:

std::eek:stream&
operator<<( std::eek:stream& lhs, const std::string& rhs )
{
lhs.write( rhs.c_str(), rhs.length());

return lhs;
}

As expected, adding this function to the program does fix the compiler
error because the compiler now needs to perform only one user-defined
conversion to find a matching function.

Greg

OK come on, use a little common sense. I think you have been mislead
because the operator<< for string is not part of the iostream library
which seem to be the only part of the STL that your reference covers.
Instad it can be found with the string class.

Try looking at the real standard section 21.3.7.9 where operator<< for
string is defined.

Or is you don't have the standard available try here

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang98/HTML/STRING2.asp

or here

http://www.sgi.com/tech/stl/basic_string.html

or here

http://www.dinkumware.com/manuals/reader.aspx?b=p/&h=string2.html#operator<<

Andy's explanation was the correct one.

john
 
K

Kai-Uwe Bux

Greg said:
Let's see whether such a operator exists in the Standard Library. Below
is the list of operator << overloads that a conforming C++ Standard
Library must implement for a std::eek:stream:

// as member functions:
ostream& operator<< (bool& val );
ostream& operator<< (short& val );
ostream& operator<< (unsigned short& val );
ostream& operator<< (int& val );
ostream& operator<< (unsigned int& val );
ostream& operator<< (long& val );
ostream& operator<< (unsigned long& val );
ostream& operator<< (float& val );
ostream& operator<< (double& val );
ostream& operator<< (long double& val );
ostream& operator<< (void*& val );
ostream& operator<< (streambuf& sb );
ostream& operator<< (ostream& ( *pf )(ostream&));
ostream& operator<< (ios& ( *pf )(ios&));
ostream& operator<< (ios_base& ( *pf )(ios_base&));

// as external functions:
ostream& operator<< (ostream& os, char ch );
ostream& operator<< (ostream& os, signed char ch );
ostream& operator<< (ostream& os, unsigned char ch );
ostream& operator<< (ostream& os, const char* str );
ostream& operator<< (ostream& os, const signed char* str );
ostream& operator<< (ostream& os, const unsigned char* str );

Source:
http://www.cplusplus.com/ref/iostream/ostream/operatorltlt.html.

The standard says the following:

1) Clause 27.2 declares that std::eek:stream is a typedef for

basic_ostream<char>

which stems from the template

basic_ostream< class CharT,
class traits=char_traits< CharT > >

2) By 21.2/2, std::string is a typedef for

basic_string<char>

which arises from

basic_string< class CharT,
class traits=char_traits<CharT>,
class Allocator=allocator<CharT> >

3) Clause 21.3.7/4-5 says that through the header <string> the following
template becomes visible:

template < class CharT, class traits, class Allocator >
basic_ostream< CharT, traits > &
As I explained in my earlier post, the fact that the Standard Library
does not define such an operator means that the compiler would have to
perform two user-defined conversions to find a matching function to
compile the code in question - one more than the Standard allows.

I would like to ask which type it is that std::string is converted to,
according to your two-conversion theory, in a call like

std::string x = "hello world!\n";
std::cout << x;

Maybe, I am missing something, but I did not find a user-defined conversion
from std::string to anywhere specified by the standard.

Your explanation is bogus: according to the standard, there is a template
that provides the operator<< for piping std::string to std::eek:stream, but
there is no user-defined conversion from std::string to anything printable
in your list above.

Of course, there is nothing stopping anyone from writing the "missing"
overloaded operator << to verify this fact:

std::eek:stream&
operator<<( std::eek:stream& lhs, const std::string& rhs )
{
lhs.write( rhs.c_str(), rhs.length());

return lhs;
}

As expected, adding this function to the program does fix the compiler
error because the compiler now needs to perform only one user-defined
conversion to find a matching function.

This code actually does not provide something missing. It is a little more
subtle. The standard, as quoted above, provides for:

template < class CharT, class traits, class Allocator >
basic_ostream< CharT, traits > &
operator<< ( basic_ostream< CharT, traits > & os,
const basic_string< CharT, traits, Allocator & str );

Now, this does not match a call to

operator<<( std::eek:stream&, something<std::string> )

because of the rules of template deduction. The second parameter in

template < class CharT, class traits, class Allocator >
basic_ostream< CharT, traits > &
operator<< ( basic_ostream< CharT, traits > & os,
const basic_string< CharT, traits, Allocator & str );

is templated. Therefore *no* user-defined conversions will be considered
[14.8.2.1]

Your code provides a non-templated overloaded version that will match since
in non-templated function arguments, user-defined conversion kicks in.


Best

Kai-Uwe Bux
 

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

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,200
Latest member
LaraHunley

Latest Threads

Top