tolower conflict with iostream?

D

David Rubin

I get an error when I compile the following code:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

using namespace std;

string&
lc(string& s)
{
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}

int
main()
{
string name = "DAVID";

cout << name << " " << lc(name) << endl;
return 0;
}

; g++ lc.cc
lc.cc: In function `std::string& lc(std::string&)':
lc.cc:11: error: no matching function for call to `transform(
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >, <unknown type>)'

If I do not include iostream, or if I use a different function
(e.g., int id(int i){return i;}), I do not get any errors. Am I doing
something wrong?

/david
 
H

Howard Hinnant

David Rubin said:
I get an error when I compile the following code:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

using namespace std;

string&
lc(string& s)
{
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}

int
main()
{
string name = "DAVID";

cout << name << " " << lc(name) << endl;
return 0;
}

; g++ lc.cc
lc.cc: In function `std::string& lc(std::string&)':
lc.cc:11: error: no matching function for call to `transform(
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >, <unknown type>)'

If I do not include iostream, or if I use a different function
(e.g., int id(int i){return i;}), I do not get any errors. Am I doing
something wrong?

Yes, but you're in good company. This one gets almost everyone at least
once.

There are two kickers (if not more) in this one:

1. tolower is (also) a template function prototyped in <locale>:

template <class charT> charT tolower(charT c, const locale& loc);

2. Any standard C++ header is allowed to include any other standard C++
header as an implementation detail (reference 17.4.4.1/1).

So when you say:

transform(s.begin(), s.end(), s.begin(), tolower);

and if the templated tolower is in scope, then the compiler can't figure
out what template parameters to try out for tolower<charT>, even if the
non-templated tolower is also in scope. And apparently gcc's <iostream>
brings tolower<charT> into scope as an implementation detail. Thus the
error.

This is a sneaky one in that it may well compile and do what you want
with another compiler. For example it works just fine on Metrowerks,
unless <locale> is explicitly included, and then you get a similar error.

You can correct it, portably, with the following incantation:

transform(s.begin(), s.end(), s.begin(), (int (*)(int))tolower);

I.e. cast tolower to the specific function pointer type that you're
aiming for.

-Howard
 
S

Steven C.

I get an error when I compile the following code:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

using namespace std;

string&
lc(string& s)
{
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}

int
main()
{
string name = "DAVID";

cout << name << " " << lc(name) << endl;
return 0;
}

; g++ lc.cc
lc.cc: In function `std::string& lc(std::string&)':
lc.cc:11: error: no matching function for call to `transform(
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
__gnu_cxx::__normal_iterator<char*, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > >, <unknown type>)'

If I do not include iostream, or if I use a different function
(e.g., int id(int i){return i;}), I do not get any errors. Am I doing
something wrong?

/david

--
Andre, a simple peasant, had only one thing on his mind as he crept
along the East wall: 'Andre, creep... Andre, creep... Andre, creep.'
-- unknown
_____________________________________________________________
the way I fixed it is by doing
::tolower
 
D

Dietmar Kuehl

Howard Hinnant said:
You can correct it, portably, with the following incantation:

transform(s.begin(), s.end(), s.begin(), (int (*)(int))tolower);

I.e. cast tolower to the specific function pointer type that you're
aiming for.

See, even standard library implementers get it wrong :) This one is,
however, even more subtle: 'std::tolower(int)' can only be called with
values which can be represented by an 'unsigned char' and EOF. However,
on systems where 'char' is a signed entity lots of possible 'char' values,
promoted to 'int', cannot be represented as 'unsigned char'. Although the
above code would be portably compilable, it won't run portably. For a
truely portable solution, you would need an auxiliary function:

char mytolower(char c) {
return std::tolower(static_cast<unsigned char>(c));
}

This will convert the negative 'char' to some positive value first which
stays the same value when converted to 'int'. Promoting a negative 'char'
directly to 'int' and then converting this value to an unsigned type
would yield a value which is [normally (*)] bigger than any value which
can be hold by an 'unsigned char'.

(*): if the number of bits used for 'int' and 'unsigned char' is identical,
it would work...
 
T

tom_usenet

Yes, but you're in good company. This one gets almost everyone at least
once.

There are two kickers (if not more) in this one:

1. tolower is (also) a template function prototyped in <locale>:

template <class charT> charT tolower(charT c, const locale& loc);

2. Any standard C++ header is allowed to include any other standard C++
header as an implementation detail (reference 17.4.4.1/1).

So when you say:

transform(s.begin(), s.end(), s.begin(), tolower);

and if the templated tolower is in scope, then the compiler can't figure
out what template parameters to try out for tolower<charT>, even if the
non-templated tolower is also in scope.

It's not that clear in the standard that the non-template tolower
shouldn't be chosen. For the templated tolower, argument deduction
would fail, so only the non-template version would be left. In theory
this could be chosen unambiguously.

And apparently gcc's said:
brings tolower<charT> into scope as an implementation detail. Thus the
error.

This is a sneaky one in that it may well compile and do what you want
with another compiler. For example it works just fine on Metrowerks,
unless <locale> is explicitly included, and then you get a similar error.

Comeau compiles it fine with both <locale> and <cctype> included.
13.4/2 suggests that it might be ok, but it certainly isn't clear
either way.

http://std.dkuug.dk/jtc1/sc22/wg21/docs/cwg_active.html#115
also has vague relevence.
You can correct it, portably, with the following incantation:

transform(s.begin(), s.end(), s.begin(), (int (*)(int))tolower);

I.e. cast tolower to the specific function pointer type that you're
aiming for.

However, this still has the problem with the domain to tolower being
EOF + the range of unsigned char.

Tom
 
J

J. Campbell

David Rubin said:
I get an error when I compile the following code:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

using namespace std;

string&
lc(string& s)
{
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}

int
main()
{
string name = "DAVID";

cout << name << " " << lc(name) << endl;
return 0;
}

david, I don't know that much about c++, but you can do what you want
by flipping the bit in the 32's place. If you know that your string
contains letters, then you can use these 3 functions:

#include <iostream>
#include <string>
using namespace std;

string lcase(string stringin){
string stringout;
for(int i = 0; i < stringin.size(); ++i)
stringout += (stringin | 32);
return stringout;
}

string ucase(string stringin){
string stringout;
for(int i = 0; i < stringin.size(); ++i)
stringout += (stringin & (223));
return stringout;
}

string flipcase(string stringin){
string stringout;
for(int i = 0; i < stringin.size(); ++i)
stringout += (stringin ^ 32);
return stringout;
}

int main(){
cout << "Enter text";
string test;
getline(cin, test);
cout << "Original " << test << endl
<< "All Caps " << ucase(test)<< endl
<< "Lower Case " << lcase(test) << endl
<< "Flip Case " << flipcase(test) << endl;
}

Of course, you will prob. want to clean up the function masks so that
they only effect characters. (eg only effect chars (65 - 90) and (97 -
122)).
 
J

J. Campbell

Steven C. said:
I get an error when I compile the following code:

#include <algorithm>
#include <cctype>
#include <iostream>
#include <string>

using namespace std;

string&
lc(string& s)
{
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}

int
main()
{
string name = "DAVID";

cout << name << " " << lc(name) << endl;
return 0;
}

I'm sure your aren't interested, but...I improved the functions I just
wrote so that they now ignore all non alphebetic characters. You can
easily change the function to pass a string reference if you want the
change to affect the original string. Anyway...these functions should
work with any string object.

#include <iostream>
#include <string>
using namespace std;

string lcase(string in){
string stringout;
for(int i = 0; i < in.size(); ++i)
if(!(in & 128) & ((in & 95) > 64) & ((in & 31) <= 26))
stringout += (in | 32);
else stringout += in; //character wasn't a letter...dont
change
return stringout;
}

string ucase(string in){
string stringout;
for(int i = 0; i < in.size(); ++i)
if(!(in & 128) & ((in & 95) > 64) & ((in & 31) <= 26))
stringout += (in & (223));
else stringout += in; //character wasn't a letter...dont
change
return stringout;
}

string flipcase(string in){
string stringout;
for(int i = 0; i < in.size(); ++i)
if(!(in & 128) & ((in & 95) > 64) & ((in & 31) <= 26))
stringout += (in ^ 32);
else stringout += in; //character wasn't a letter...dont
change
return stringout;
}

int main(){
cout << "Enter text\n";
string test;
getline(cin, test);
cout << "Original " << test << endl
<< "All Caps " << ucase(test)<< endl
<< "Lower Case " << lcase(test) << endl
<< "Flip Case " << flipcase(test) << endl;
}



in case you are wondering...

if(!(in & 128) & ((in & 95) > 64) & ((in & 31) <= 26))
------------- ------------------ -------------------
0XXX XXXX X10X XXXX 000X XXXX <= 26
000X XXXX > 0
 
D

David Rubin

J. Campbell said:
using namespace std;

string&
lc(string& s)
{
transform(s.begin(), s.end(), s.begin(), tolower);
return s;
}
[snip]
david, I don't know that much about c++, but you can do what you want
by flipping the bit in the 32's place. If you know that your string
contains letters, then you can use these 3 functions:
[snip - code]

Thanks for the code. However, you are assuming an ASCII character set
and a particular locale. The ::tolower solution seems to work well. I
suppose you can extend this to implement Flipcase as a functor (or
simply as a function) using

s = :):isupper(s) ? ::tolower(s) : ::toupper(s));

BTW, for those in the know who are still reading this thread, is* and
to* are known to take an int argument to account for EOF. However, is it
safe to assume that an explicit cast is unnecessary when applying these
functions to a string? Presumably, a string will not contain EOF.

/david
 
D

Dietmar Kuehl

David said:
s = :):isupper(s) ? ::tolower(s) : ::toupper(s));

BTW, for those in the know who are still reading this thread, is* and
to* are known to take an int argument to account for EOF. However, is it
safe to assume that an explicit cast is unnecessary when applying these
functions to a string? Presumably, a string will not contain EOF.


Although the 'is*()' and 'to*()' account for EOF, they do not account
for negative values of 'char' on platforms where 'char' is a signed
entity (at least, they are not required to; I would expect that library
implementers actually do account for this case). Thus, you have to cast
a 'char' to 'unsigned char' before passing them into these functions.
 
S

Shane Beasley

Dietmar Kuehl said:
Although the 'is*()' and 'to*()' account for EOF, they do not account
for negative values of 'char' on platforms where 'char' is a signed
entity (at least, they are not required to; I would expect that library
implementers actually do account for this case). Thus, you have to cast
a 'char' to 'unsigned char' before passing them into these functions.

#include <algorithm>
#include <string>
#include <cctype>

template <int (*F) (int)>
int safe (unsigned char c) { return F(c); }

int main () {
std::string s;
std::transform(s.begin(), s.end(), s.begin(), safe<std::toupper>);
}

As a bonus, this also deals with the overloaded stuff in <locale> --
unless you actually wanted to call those. :)

- Shane
 
D

Dietmar Kuehl

template <int (*F) (int)>
int safe (unsigned char c) { return F(c); } [...]
std::transform(s.begin(), s.end(), s.begin(), safe<std::toupper>);

That's a nice one! :)
 
T

tom_usenet

#include <algorithm>
#include <string>
#include <cctype>

template <int (*F) (int)>
int safe (unsigned char c) { return F(c); }

Nice idea, but how about:

#include <string>
#include <functional>

template <int (*F) (int)>
struct safe_to: std::unary_function<char, char>
{
char operator()(char c) const {
return std::char_traits<char>::to_char_type(
F(std::char_traits<char>::to_int_type(c))
);
}
};

With luck, everything will be inlined since safe_to is a functor, and
F is a compile time constant.

Tom
 
D

David Rubin

tom_usenet said:
#include <string>
#include <functional>

template <int (*F) (int)>
struct safe_to: std::unary_function<char, char>
{
char operator()(char c) const {
return std::char_traits<char>::to_char_type(
F(std::char_traits<char>::to_int_type(c))

I'm not sure what to_int_type does, but you don't want to promote a
negative char value to a negative int value. You want to promote a
negative char value to a non-negative (e.g., unsigned char) value, and
*then* to an int value (implicit in the call to std::tolower).

/david
 
T

tom_usenet

I'm not sure what to_int_type does, but you don't want to promote a
negative char value to a negative int value. You want to promote a
negative char value to a non-negative (e.g., unsigned char) value, and
*then* to an int value (implicit in the call to std::tolower).

to_int_type and to_char_type are for correctly converting between char
and int in the streams sense (e.g. map char to unsigned char values).
It saves on ugly casts (although looks pretty ugly itself). It also
readily converts between wchar_t and wint_t -
char_traits<wchar_t>::to_char_type(mywint);

Tom
 

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,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top