Solved

Could someone try to explain this weird compiler behaviour

Posted on 2003-11-18
8
817 Views
Last Modified: 2013-12-14
Hi,

In reference to this thread ' http://www.experts-exchange.com/Programming/Programming_Languages/Cplusplus/Q_20800597.html ' I had some difficulties with one of the classes I'm working on. Well I decided to take a look at how Microsoft is doing these things and what do I find out? They have the same code as I have, but theirs is working?! The same you say, yes the same. Are you sure, hey look, there IS something different. Find the two differences between the code :-) (I posted the solutation below it ;-))

class CTest
{
public:
      CTest() throw()
      {
            m_pszData = new TCHAR[1024];
      }

      ~CTest()
      {
            delete [] m_pszData;
      }

      TCHAR& operator[] (int iChar) const throw()
      {
            return m_pszData[iChar] ;
      }

      operator LPCTSTR() const throw()
      {
            return m_pszData ;
      }

private:
      LPTSTR m_pszData;
};

=============================================

class CTest
{
public:
      CTest() throw()
      {
            m_pszData = new TCHAR[1024];
      }

      ~CTest()
      {
            delete [] m_pszData;
      }

      TCHAR& operator[] (UINT iChar) const throw()
      {
            return m_pszData[iChar] ;
      }

      operator LPCTSTR() const throw()
      {
            return m_pszData ;
      }

private:
      LPTSTR m_pszData;
};

The first one compiles, the second one doesn't. The second one gives an error that the operator are ambigious. Why? Because if you use the [] operator on the second one, the compiler has two choices:
1) Use the provided [] operator
2) Use the operator to cast to LPCTSTR and then use the default [] operator for LPCTSTR

So why isn't it doing that at the first example? Well because of a very small change in the [] operator. They use an int instead of an unsigned int.

Now I hope someone is able to explain me why changing the signed/unsigned state of a parameter can make such a big difference.
0
Comment
Question by:LuCkY
  • 4
  • 2
  • 2
8 Comments
 
LVL 15

Accepted Solution

by:
efn earned 63 total points
ID: 9775575
Welcome to the dark and mysterious world of function overload resolution.

When there is more than one candidate for a function call, the compiler tries to pick the one that requires the fewest and least drastic conversions.  Converting the object reference to LPCTSTR is a conversion, and converting a signed int subscript to unsigned is also a conversion.  (Signed int is the type of a plain old numeric literal without a decimal point or leading zero.) There is a ranking of conversions--some are considered less drastic and therefore preferred to others, but the compiler apparently considers converting the subscript to unsigned or converting the object reference to a pointer equally disgusting, so you get the ambiguity error.  When the operator [] parameter is int, no conversion is necessary for a call to operator [], so that function beats out operator LPCTSTR.

That means that if you use an unsigned subscript, the second one will compile and the first one won't.

This doesn't quite match up with what I've read about overload resolution, which says that the compiler should prefer a standard conversion such as signed to unsigned to a user-defined conversion, which I think is what operator LPCTSTR is.  I reproduced your result with Microsoft Visual C++ 6.0, but Borland C++Builder 4 was quite happy to compile either version with a signed int subscript, so maybe it follows the standard better in this detail.

--efn
0
 
LVL 2

Assisted Solution

by:ext2
ext2 earned 62 total points
ID: 9775697
Very interesting.  MSVC++6 gives little info, but running this on the free/open GNU g++ compiler (available in Cygwin, www.cygwin.com, under Windows) reports the following on the second example:

op2.cc: In function `int main()':
op2.cc:41: error: ISO C++ says that `TCHAR& CTest::operator[](unsigned int)
   const' and `operator[]' are ambiguous even though the worst conversion for
   the former is better than the worst conversion for the latter

Moreover, if we put both

  TCHAR& operator[] (UINT iChar) const throw()

and

  TCHAR& operator[] (int iChar) const throw()

into the class, the error doesn't occur!  and running something like

  cout << test[(UINT)2] << test[2];

invokes each method in turn.

The problem has something to do with the

  operator LPCTSTR() const throw()

causing an implicit conversion of the object into a C string, which in turn has an "operator[]" operator built-in as well.  That's where the ambiguity is, but I'm not sure what the specific rules are how it choosing one of multiple functions.  Why keep it simple and replace

  operator LPCTSTR() const throw()

with

  LPCTSTR c_str() const throw()

as the STL string class does?
0
 
LVL 2

Expert Comment

by:ext2
ID: 9775756
What efn said about

  "That means that if you use an unsigned subscript, the second one will compile and the first one won't."

I've also verified as true on MSVC++6, but on g++ both compile if you use an unsigned subscript, and in both cases the "operator[] (int/UNIT iChar)" gets invoked rather than the "operator LPCTSTR()".  As I mentioned before, MSVC++6 and g++ behave identically, however, if a signed subscript is used.

0
Active Directory Webinar

We all know we need to protect and secure our privileges, but where to start? Join Experts Exchange and ManageEngine on Tuesday, April 11, 2017 10:00 AM PDT to learn how to track and secure privileged users in Active Directory.

 
LVL 2

Expert Comment

by:ext2
ID: 9775758
What efn said about

  "That means that if you use an unsigned subscript, the second one will compile and the first one won't."

I've also verified as true on MSVC++6, but on g++ both compile if you use an unsigned subscript, and in both cases the "operator[] (int/UNIT iChar)" gets invoked rather than the "operator LPCTSTR()".  As I mentioned before, MSVC++6 and g++ behave identically, however, if a signed subscript is used.

0
 
LVL 1

Author Comment

by:LuCkY
ID: 9777152
Very interesting that the compiler thinks that using an signed integer w/o the conversion will be faster, which I seriously doubt. Assuming that we always take the path of the user defined [] operator there are two possibilities:

TCHAR& operator [] (UINT uPos) const throw()
{
    // Only error checking for upper bounds has to be done, so one if statement
}

TCHAR& operator [] (int nPos) const throw()
{
    // Here however I also have to check the lower bounds
}

I wonder if that a signed to unsigned conversion, especially if you use a constant value such as in our tests (ie strString[0] = 'T') is faster than the extra if statement required if the conversion is not needed.

>> Why keep it simple and replace

So I can pass the string to functions that require a LPCTSTR without calling a member function to increase readability. I think the STL string also has a LPCTSTR operator hasn't it? Well I guess the follow up will be in compiler madness part #2 :-)
0
 
LVL 15

Expert Comment

by:efn
ID: 9777192
> Very interesting that the compiler thinks that using an signed integer w/o the conversion will be faster, which I seriously doubt.

The compiler is not making any judgements about how fast operations might be.  The language designers had to make some rules for resolving function overloads, they wrote the rules to favor the best match, and they decided that signed int to signed int was a better match than signed int to unsigned int.

> I think the STL string also has a LPCTSTR operator hasn't it?

No, they intentionally left it out, because they thought it was dangerous.  Not to mention that "LPCTSTR" is a Microsoftism that probably wouldn't be used in a standard.

--efn
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9777526
>> Not to mention that "LPCTSTR" is a Microsoftism that probably wouldn't be used in a standard.

Well const TCHAR * then :-)

>> The compiler is not making any judgements about how fast operations might be

It is, the compiler changes a lot of your code -if it is safe to do so- if it can speed up things (depending on the optimization settings too of course). In my example it wont spot the extra if statement of course. That was just to give an example of why something that the compiler considers to be faster can actually be slower.

>> and they decided that signed int to signed int was a better match than signed int to unsigned int.

That's not exactly what's happening. They decided a user defined conversion (from which the speed is unknown, might as well have a huge loop in it) and after that using an operator with a signed int is faster than converting a signed int to unsigned int. Makes sense doesn't it :-D
0
 
LVL 2

Expert Comment

by:ext2
ID: 9784109
As efn implies, the use of "faster" is misleading as it suggests how long it takes for the compiled code to execute at run-time (i.e. performance).  "simpler" or "fewer steps" is better as it suggests how many logical steps are in the transformation that is to be done at compile-time regardless how fast it takes the compiler to perform those transformations or how fast the compiled code actually runs.

0

Featured Post

Announcing the Most Valuable Experts of 2016

MVEs are more concerned with the satisfaction of those they help than with the considerable points they can earn. They are the types of people you feel privileged to call colleagues. Join us in honoring this amazing group of Experts.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article will show you some of the more useful Standard Template Library (STL) algorithms through the use of working examples.  You will learn about how these algorithms fit into the STL architecture, how they work with STL containers, and why t…
Programmer's Notepad is, one of the best free text editing tools available, simply because the developers appear to have second-guessed every weird problem or issue a programmer is likely to run into. One of these problems is selecting and deleti…
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

820 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question