|
I'm using VS 7.0 and I don't see these warnings, even with the latest version. Could you post the exact line numbers you get with the new version. No doubt it's just a static_cast<> needed somwhere but I'd like to be sure.
-Joe
|
|
|
|
|
When compling in BCB 6.0 there come the errors:
1.[C++ Error] StdString.h(2666): E2268 Call to undefined function 'ssnprintf'
2.[C++ Error] StdString.h(1576): E2227 Extra parameter in call to isspace(int)
I use the class as following:
1.
CStdString str;
int iID = 1000;
str.Format("%d", iID);
2.
CStdString str = " Good! ";
str.Trim();
What's wrong?Please help!Thanks!
|
|
|
|
|
There is nothing wrong with your code. The problem is in my preprocessor flags. I've got a check for _MSC_VER in there that doesn't belong and I've got to remove and test it. I added it in recently while incorporating some changes from someone else designed to make the code work on another platform. The problem with making such changes is that I am unable to then re-test the code on all other platforms -- leading to problems such as this one.
I'll post back here in a day or two when I fix it. Sorry.
-Joe
|
|
|
|
|
First, thx for this great piece of work !
I don't know if you plan to be strictly compliant to CString or if you plan to extend your class to new methods and functionalities.
Anyway I found useful to add it some stuff like :
- the Tokenize function of the ATL strings :
MYTYPE Tokenize(PCMYSTR tok, int &nFirst) const
{
int nPrev;
if ( nFirst < 0 )
nFirst = 0;
if ( nFirst >= size() )
return MYTYPE();
nPrev = nFirst;
nFirst = this->find_first_of(tok, nPrev);
if (nFirst == npos)
nFirst = size();
return this->substr(static_cast<mysize>(nPrev), static_cast<mysize>(nFirst++ - nPrev));
}
- and conversion stuff between primary types and strings :
operator const int() const
{
return atoi(c_str());
}
operator const unsigned int() const
{
return atoi(c_str());
}
operator const long() const
{
return atol(c_str());
}
operator const unsigned long() const
{
return atol(c_str());
}
operator const __int64() const
{
return _atoi64(c_str());
}
operator const unsigned __int64() const
{
return _atoi64(c_str());
}
operator const double() const
{
return atof(c_str());
}
CStrEx(int i)
{
Format("%d", i);
}
CStrEx(unsigned int ui)
{
Format("%u", ui);
}
CStrEx(long l)
{
Format("%ld", l);
}
CStrEx(unsigned long ul)
{
Format("%lu", ul);
}
CStrEx(__int64 i64)
{
Format("%i64d", i64);
}
CStrEx(unsigned __int64 ui64)
{
Format("%ui64u", ui64);
}
CStrEx(double d)
{
Format("%g", d);
}
Ok, I admit the constructors are notessential
|
|
|
|
|
BTW, I renamed your class to CStringEx for my own use
(sounds better to me )
but you keep all credits in the header comments don't worry
|
|
|
|
|
Hi,
I'm glad you got good use out of the class.
The main reason I don't add conversion operators and constructors such as the ones you mentioned is that they lead to unintended side effects. Read up on Scott Myers for more on this subject but the basic message is "avoid user-defined conversion operators whenever possible."
I've even had to fight the debate over the one user-defined conversion that I DID put in there -- operator const CT* -- which just calls c_str(). There is no such operator in basic_string due to the dangers I mentioned above. But I was so used to it from CString that I simply had to have it. The convenience is more than worth what is a minor risk, IMHO. Just be aware that some C++ purists do not like that.
Still the argument against conversion operators is a very sound one. In the very least, those constructors you mentioned should be declared with the "explicit" keyword.
Anyway at this point the class is almost 7 years old. As I have tried to keep it working on all platforms (Windows, Unix, Linux, Solaris, etc) I have basically reached the point where I do not want to add any more functions to it. It is tough enough to keep it working. In fact, right now I'm working on a fix for "billca" (see the recent feedback below).
But you are of course free to add or change anything you want. I'd still recommend against those operators and constructors, but hey, go crazy.
Please let me know if you have any problems.
-Joe
|
|
|
|
|
Joe, in your comments within the function ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl), you say you'd like to hear about compile errors related to the various versions of vswprintf on the various platforms. In order to get a clean compile on HP-UX and LINUX, I changed:
#if (!defined(_MSC_VER) &&
!defined (__BORLANDC__) &&
!defined(__GNUC__) &&
!defined(__sgi)
to
#if (!defined(_MSC_VER) &&
!defined (__BORLANDC__) &&
!defined(__GNUC__) &&
!defined(__sgi) &&
!defined(HPUX1100)) ||
defined(LINUX)
and
#elif defined(__sgi)
to
#elif defined(__sgi) || defined(HPUX1100)
I don't know if these new definitions would be the right ones to be used generally, but they seem to work for my installation.
Bill
|
|
|
|
|
This is great stuff. And I have no way of knowing it since all I've got right now is a Windows box. I will definitely add this in to the preprocessor headers on the on-line version. You get a big credit in the header for this (and for all the other stuff you're helping out with here).
I'll try to coordinate with you off-line testing all the changes I've made once we get all these matters resolved and I'll post a new version soon.
Thanks!
-Joe
|
|
|
|
|
Joe, since CStdString doesn't claim to be exactly the same as CString and since CString of course doesn't even work at all on the UNIX platforms that CStdString does, I'm not sure the following should be considered a problem with CStdString, and I don't know if you'll want to change anything, but it certainly is an additional pitfall that one should be aware of when using CStdString in place of CString. Using a simple example, with UNICODE defined, say you code something like:
mystring1.Format(L"%s", (const wchar_t*)mystring2);
With CString, the %s (lower case) format specification is correct as it is with CStdString on Windows, but using CStdString and running on Solaris (and probably other UNIXs too), it needs to be %S (upper case). The underlying vswprintf function apparently expects it that way.
So for platform-independence, rather than have two versions of every Format call, I couldn't think of anything else but to leave the %s(s) and make the necessary adjustment inside StdString.h as follows:
In ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl), after the #if !defined(_MSC_VER)..., I replaced
return vswprintf(pW, nCount, pFmtW, vl);
with
std::basic_string<wchar_t> pFmtW2 = pFmtW;
std::basic_string<wchar_t>::size_type index = 0;
while ((index = pFmtW2.find(L"%s", index)) != std::basic_string<wchar_t>::npos)
{
pFmtW2.replace(index + 1, 1, L"S");
index += 2;
}
return vswprintf(pW, nCount, pFmtW2.c_str(), vl);
I realize this is a bit of a kludge and you may not want to do anything like it in the real thing, but I thought you should know and I'd be interested in your thoughts.
Bill
|
|
|
|
|
Hi Bill,
My general thoughts here are not to try to do this. I have two reasons. One is a philosophical one, and the other a practical one.
The philosophical reason for not making the change is this: In essence, what you are doing is trying to protect the programmer from himself (or herself). What he/she is doing here is violating a rule of C++. The Format() function has always been just a thin veneer or sprintf/vswprintf. That was always clear, even from CString. The only convenience it is supposed to add is doing the memory/buffer management for you. THAT is the part that is truly a pain with sprintf. Admittedly, I have made such a protect-them-from-themselves change myself with the entire SS_SAFE_FORMAT fix I put in there. But my fix is completely unobtrusive, adds no danger, and can be disabled via macro. Furthermore, having made such a change once, I hope never to have to do so again.
The practical reason is that your solution might actually mess up valid code. Let's take your example
mystring1.Format(L"%s", (const wchar_t*)mystring2);
and change it to the following
mystring1.Format(L"%s %S", *)"this is a thin string", L"and this is a wide string");
In this case, the programmer intends the first '%s' to be a lowercase 's' because the string argument he/she supplied was a char-based string constant. If Format() were to change this to '%S' it would cause a problem.
And since those two string literal arguments at the end of the function call in this example are part of the variable argument list, there is no way for the compiler to know what they really are. We have no way of divining the programmers intentions except through the format specifiers themselves. If we cannot rely on THOSE being correct then we might as well throw in the towel.
So that is why I would hesitate to make this change.
-Joe
|
|
|
|
|
For what it's worth, I too have thought all along that you probably shouldn't change any code for this, but to me the issue is perhaps a bit more significant than I gather you're thinking, and noteworthy (whether for documentation or just in your mind) if only because I think others will inevitably and innocently fall into this trap. I probably didn't explain my thoughts clearly enough at first as to why I think it's significant -- or at least interesting. Not to try to persuade any action on your part, but just as explanation and at the risk of boring you...
As I said, although you don't claim that CStdString is exactly the same as CString, the fact is that one can take an MFC application, change the CStrings to CStdStrings and (aside from some possible tweaks for compilation purposes) run it on UNIX -- this is what makes CStdString so great. Other than the StdCodeCvt problem you and I are currently discussing elsewhere, this is the only runtime issue I have encountered related to CString/CStdString replacement that requires changes to my applications (well, since I decided not to use SS_SAFE_FORMAT, I also had some changes to make related to that), which alone makes this issue special. And when first addressing the issue, it's particularly hard to figure out -- there's no crash, the strings just don't get formatted correctly. Considering it purely from this angle -- as a replacement for CString -- it is very much like your SS_SAFE_FORMAT issue, as you pointed out.
With the vs(n)printf and vs(n)wprintf functions on Windows, which I assume the CString Format function uses or at least emulates, '%s' means a thin string and '%S' means a wide string for vsprintf, but the opposite for vswprintf where '%s' means wide and '%S' thin! Apparently CString.Format works the same way. So with UNICODE defined, '%s' means the argument is a *wide* string. And with UNICODE not defined, the same symbol, '%s', means *thin* string. So the application code stays the same (i.e. lower case 's') whether built for UNICODE or not, which is nice. The only time you would use '%S' with CString is when you had a string argument that is opposite in thinness/wideness from the CString itself. Incidently, while the point made by your counter example is well-taken, the implication is that the example would not be correct if mystring1 were a UNICODE-enabled CString (the format symbols would need to be reversed) -- maybe you realize this and didn't intend to say anything to the contrary.
Now with CStdString -- and running on UNIX (Solaris, at least) -- this is NOT the case. Granted, CString doesn't even work at all on UNIX (otherwise I suppose it would have to deal with this same issue), so understandably CStdString is taking on a much bigger burden. But because the underlying vswprintf function on UNIX (unlike the Windows version, for whatever reason) expects '%S' for wide strings, all the Format '%s's in a UNICODE-enabled application would need to be changed to '%S'.
Now I chose to leave all the Format '%s's in my application code as lower case and programmatically convert them to '%S' inside the ssvsprintf function (as well, I suppose, for consistency change any '%S's to '%s'). But as you pointed-out of course this only works when you assume there are no Format calls, in a UNICODE-enabled application, that include a thin string argument with the "correct" (correct for UNIX, that is) symbol, '%s'. For me this won't happen because all my CStdString.Format calls (mostly former CString.Format ones -- and a few new ones) are coded the CString way. So in the unlikely event I ever did include a thin string argument, I would use the '%S' symbol and it would ultimately be handled correctly.
And so my point is simply that this issue, which I tend to think would be encountered by many, and the subsequent changes required, is significant because it is really the one and only hindrance to what is otherwise a practically flawless CString/CStdString migration.
Bill
|
|
|
|
|
Joe, running on Solaris I came across what appears to be a problem with the source-length parameter on the StdCodeCvt call in seven out of the eight ssasn and ssadd functions in which this call is made. (Curiously, although these eight functions are similar and are otherwise coded consistently, the odd one, that does seem to work properly as is, has the source-length parameter inconsistent with the others.) In each instance, an assertion in StdCodeCvt fails, then the program crashes with "Error 115 Unexpected signal (Signal 6)". The exact lines in question, and what I did to correct them, are indicated below in the comments of a simple program I used to test each of the assignment/concatenation functions. If you don't think these changes were the right thing to do or if you have any questions/comments, please let me know.
#include "StdString.h"
int main()
{
CStdString ansiStdString = L"";
// With UNICODE not defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PSTR pA, PCWSTR pW, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), pW, nLen);
//
// to
//
// StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), pW, nLen+1);
//
// in ssasn(std::string& sDst, PCWSTR pW).
std::basic_string<wchar_t> wideBasicString = L"";
ansiStdString = wideBasicString;
// With UNICODE not defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PSTR pA, PCWSTR pW, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), sSrc.c_str(), nLen);
//
// to
//
// StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()), sSrc.c_str(), nLen+1);
//
// in ssasn(std::string& sDst, const std::wstring& sSrc).
ansiStdString += wideBasicString;
// With UNICODE not defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PSTR pA, PCWSTR pW, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()+nDstLen), sSrc.c_str(), nSrcLen);
//
// to
//
// StdCodeCvt(const_cast<SS_PTRTYPE>(sDst.data()+nDstLen), sSrc.c_str(), nSrcLen+1);
//
// in ssadd(std::string& sDst, const std::wstring& sSrc).
CStdString wideStdString = "";
// With UNICODE defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PWSTR pW, PCSTR pA, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()), pA, nLen+1);
//
// to
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()), pA, nLen);
//
// in ssasn(std::wstring& sDst, PCSTR pA).
wideStdString += "";
// With UNICODE defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PWSTR pW, PCSTR pA, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDstLen), pA, nSrcLen+1);
//
// to
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDstLen), pA, nSrcLen);
//
// in ssadd(std::wstring& sDst, PCSTR pA).
std::basic_string<char> ansiBasicString = "";
wideStdString = ansiBasicString;
// With UNICODE defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PWSTR pW, PCSTR pA, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()), sSrc.c_str(), nLen+1);
//
// to
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()), sSrc.c_str(), nLen);
//
// in ssasn(std::wstring& sDst, const std::string& sSrc).
wideStdString += ansiBasicString;
// With UNICODE defined, this line causes:
//
// Assertion failed: (SSCodeCvt ::ok == res)
// in StdCodeCvt(PWSTR pW, PCSTR pA, int nChars, const std::locale& loc=std::locale()).
//
// Corrected by changing
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDstLen), sSrc.c_str(), nSrcLen+1);
//
// to
//
// StdCodeCvt(const_cast<SW_PTRTYPE>(sDst.data()+nDstLen), sSrc.c_str(), nSrcLen);
//
// in ssadd(std::wstring& sDst, const std::string& sSrc).
return 0;
}
Bill
|
|
|
|
|
Hi Bill,
This looks like a lot of work you've done in this one and I appreciate it. But I think that what you have done, while it works, it perhaps not the best solution.
Before I explain why, I have two questions. I don't have Solaris so I can't try this. It would greatly help me to be sure if I understand the problem correctly. This is vital to me because it seems every time I correct a bug on one platform, I cause one on another. I need to be sure I truly understand what the problem is.
To answer these you will need the original, uncorrected version of StdString.h:
1. Do you know what value codecvt::in was returning when it was giving you the error? I'm referring to your very first example here. I was ASSERTing that it should equal codecvt::ok but I suspect that in your case it instead equalled something like codecvt::partial. It would help if I knew.
2. Take your first example that was causing your problems (with UNICODE defined) and change it from this:
CStdString ansiStdString = L"";
to this:
CStdString ansiStdString = L"Hello";
Does the ASSERT still fail? My guess is that it will not but I need to know for sure.
To illustrate why, let's take a look at the overload of StdCodeCvt which is being called:
<br />
inline PSTR StdCodeCvt(PSTR pA, PCWSTR pW, int nChars,<br />
const std::locale& loc=std::locale())<br />
{<br />
ASSERT(0 != pA);<br />
ASSERT(0 != pW);<br />
pA[0] = '\0';<br />
PSTR pBadA = 0;<br />
PCWSTR pBadW = 0;<br />
SSCodeCvt::result res = SSCodeCvt::ok;<br />
const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt);<br />
SSCodeCvt::state_type st= { 0 };<br />
res = conv.out(st,<br />
pW, pW + nChars, pBadW,<br />
pA, pA + nChars, pBadA);<br />
ASSERT(SSCodeCvt::ok == res);<br />
return pA;<br />
}<br />
The 'nChars' argument is used in pointer arithmetic. I am using it to tell the codecvt::in function where the source string's null terminator character is -- it is 'nChars' characters past the beginning.
In the example you used, the source string was empty so the first character was the null terminator. But by changing that length to 1, you have effectively told the codecvt::in function to look past even the null terminator. That is not a valid thing to do but I suspect we're just "getting away with it".
My suspicion is that the real problem lies in the fact that I'm calling codecvt::in (or codecvt::out on the other examples) on a zero-length string. I suspect that what I should really be doing is having a special case which checks for zero-length strings and does nothing.
Hope this is clear. Again I appreciate your effort in this
-Joe
|
|
|
|
|
1. Yes, the original way returns codecvt::partial.
2. What you say makes sense and you're right about the first example -- it only ASSERTS (and crashes) with an empty string. In fact, the same is true for examples 1, 2 and 3 (call this group Case 1). This is consistent in that these are the three that convert from wide to ansi and are the ones that I got to work by passing length+1 instead of length. However, the last four examples (call them Case 2) still fail the same way whether the source string is empty or not. (These four are opposites of the others in that they convert from ansi to wide and only work when you pass length instead of length+1.)
So, I agree with your assessment of the right way to handle Case 1...which leaves Case 2.
By the way if you haven't noticed already, the "odd" one of the eight related functions, which is missing from the examples due to it working as is (namely ssadd(std::string& sDst, PCWSTR pW)), essentially belongs to Case 1 and only works now because it passes length+1 (which is what prompted me to change the others the way I did)! So whatever we wind up doing, you should probably make this one consistent with the other Case 1 functions.
Bill
|
|
|
|
|
I got the impression you were going to make a fix for this (with my help for testing if necessary), but I haven't heard anything from you in a while nor seen any related change to your latest source. Are you still planning to address this? Thanks and let me know if you need any help from me.
Bill
|
|
|
|
|
Unfortunately this seems to have opened a can of worms for me regarding how I do conversions. But admittedly, I've dragged my feet here.
I'll email you directly about this and we can figure it out.
-Joe
|
|
|
|
|
I have just been fighting a strange bug in an VC++ 6.0 Win32 Console App, where I wanted to use CStdString - the 14. MAR 2003 version.
In addition, I use the 3.08 version of Dinkumware STL. I had several
"error C2039: 'std' : is not a member of 'std'".
It turned out to be related to the SS_USE_FACET-definition.
#ifndef SS_USE_FACET<br />
#if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 )<br />
#if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER)<br />
#ifdef SS_ANSI<br />
#pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!)<br />
#endif<br />
#endif<br />
#define SS_USE_FACET(loc, fac) std::use_facet<fac >(loc)<br />
#elif defined(_MSC_VER )<br />
#define SS_USE_FACET(loc, fac) std::_USE(loc, fac)<br />
<br />
#elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE)<br />
#define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0)<br />
#else<br />
#define SS_USE_FACET(loc, fac) std::use_facet<fac >(loc)<br />
#endif<br />
#endif
I emphasized the string where I had problems, and by changing this line to
#define SS_USE_FACET(loc, fac) _USE(loc, fac)
(I removed the std:: namespace specifier just before _USE...), I could compile without trouble.
I have no idea if this is "smart" to do. Any comments ?
Bob
Proud Programmer!
|
|
|
|
|
TestString.obj : error LNK2019: unresolved external symbol "class CStdStr<char> __cdecl operator+(class CStdStr<char> const &,char const *)" (??H@YA?AV?$CStdStr@D@@ABV0@PBD@Z) referenced in function _main
.\Debug/TestString.exe : fatal error LNK1120: 1 unresolved externals
|
|
|
|
|
http://home.earthlink.net/~jmoleary/code/StdString.zip
|
|
|
|
|
Well now, that would explain why I was unable to reproduce this bug today. I already fixed it!
Thanks Baryon.
I haven't seen this linking bug since way back when the code came in two files (a .H file and a .CPP file). I'm not sure if the original poster (Kurt) had that ancient version or it was some other thing but I certainly can't make this happen in VS.NET
Kurt, if you download the latest version and you find problems, post back here
-Joe
|
|
|
|
|
I'm getting the following compile error on HP-UX 11.0:
Error 419: "StdString.h", line 1608 #
'basic_string<#1,std::char_traits,std::allocator>' is used as a type, but has not been defined as a type.
typedef typename std::basic_string<CT> MYBASE; // my base
On my other UNIX platforms, I don't get this error. Do you know what's wrong? Thanks for your help.
Bill
|
|
|
|
|
Hi,
Sorry it took me so long to respond. I must have missed the email notifying me of this.
Hi Bill,
The problem is a simple one. The easiest way to fix it is to edit the line yourself. In your version of the code, Line 1608 reads like this:
typedef typename std::basic_string MYBASE; // my base
When instead it should read like this:
typedef typename std::basic_string<ct> MYBASE; // my base
But the better solution is probably to put the version you have aside and replace it with the most up-to-date version where it has been fixed for some time. You can always get that version here:
http://www.joeo.net/code/StdString.zip
Don't delete your old version -- perhaps you've made changes already that you want to keep. And I never know when some change I make that fixes things on one platform will break it on another. But in general this should fix your problem.
-Joe
|
|
|
|
|
I am using the latest version, which has:
typedef typename std::basic_string<ct> MYBASE; // my base
Could it be something else with this particular compiler?
Bill
|
|
|
|
|
By the way, I just figured-out that in the previous two messages the "<CT>" does not appear in the line:
typedef typename std::basic_string<CT> MYBASE; // my base
Bill
|
|
|
|
|
By the way, the "<CT>" doesn't show-up in the previous two messages. It should say:
typedef typename std::basic_string<CT> MYBASE; // my base
Bill
|
|
|
|
|