Solved

Small yet annoying (template) overloading problem

Posted on 2003-11-17
21
446 Views
Last Modified: 2010-04-01
Hi,

I'm constantly getting this compiler error and there are workarounds, but I prefer readability above those workarounds :-)

I will give you a simplified version of the code:

template <typename T>
class CBuffer
{
public:
    operator T * () const throw()
    {
        return m_buffer;
    }

    T& operator [] (UINT uPos) const throw()
    {
        return m_buffer[uPos];
    }

private:
    T * m_buffer;
};

class CString : protected CBuffer<TCHAR>
{
public:
    using CBuffer<TCHAR>::operator [];

    void Empty() throw()
    {
        (*this)[0] = '\0';
    }
};

This code gives me this error:

c:\Weltevreden Framework\Source\Utility.hpp(477) : error C2666: 'Weltevreden::Utility::CBuffer<T>::operator`[]'' : 2 overloads have similar conversions
with
[
    T=TCHAR
]
c:\Weltevreden Framework\Source\Utility.hpp(139): could be 'TCHAR &Weltevreden::Utility::CBuffer<T>::operator [](UINT) throw()'
with
[
    T=TCHAR
]
or       'built-in C++ operator[(TCHAR , int)'
while trying to match the argument list '(Weltevreden::Utility::CString, int)'

Can easily be solved by either commenting out the T * operator from CBuffer or by using an "elementAt" function instead of the "operator []". In this case it may even be better readable, but I still prefer to have the "operator []" working and it gives the same error if I use it elsewere (also want to keep the T * operator by the way :-)). The strange thing is that I don't use the type casting operator in the CString class (ie I don't do this: "using CBuffer<TCHAR>::operator T * ();"). So basically the compiler should be ignoring that operator all together as I use the CBuffer as a protected subclass shouldn't it?

Well either way, if you have an idea on how to solve this (or some suggestive imput) please share :-)
0
Comment
Question by:LuCkY
  • 8
  • 8
  • 4
  • +1
21 Comments
 
LVL 19

Expert Comment

by:Dexstar
ID: 9765283
LuCkY:

> Well either way, if you have an idea on how to solve this (or some suggestive
> imput) please share :-)

You don't really need both of those, because they do almost exactly the same thing.  If you want to keep them both, then you're going to have to clarify to the compiler which one you mean whenever you use one or the other.  I would omit the operator T*() ...  The [] operator allows you to check the index being selected and handle an error if it exceeds the allocated size.  And if you really want a T* value, you can always do this:  &(str[0]) .

Hope That Helps,
Dex*
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9765355
I do need both of them as there is a big difference:

For instance I have this function:

void BogusFunction(LPCTSTR) { }

I can use this code:

CString strString;
BogusFunction(strString);

Which can only be achieved by using a T * operator (I think :-), atleast not with a [] operator).

As I said, readability matters for me, I know I can do &(strString[0]), but that's not what I want. I'd rather use a getPointer() function or something like that then.
0
 
LVL 19

Expert Comment

by:Dexstar
ID: 9765392
Okay, well, if you want to use the T* operator for const values (like LPCTSTR), then the compiler might not complain.  Try changing it to this:

    operator const T* () const throw()
    {
        return m_buffer;
    }

Dex*
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9765410
Hmmm, nope, that doesn't do the trick. It wouldn't be a totally satisfying solution either as I don't want the CBuffer to return a const value. The CString however will get a const operator (as soon as I get this working that is :-))
0
 
LVL 19

Expert Comment

by:Dexstar
ID: 9765502
Well, sometimes you'll want a const pointer, and sometimes you won't, so you'll probably end up having both versions in your class when you're all done.  And, if you only have one, it should be the const...

But, anyway, I'm not sure of how to make both you and the compiler happy.  I think you may want to have protected "getPointer" function like you said, and use that in your Empty() function.

Dex*
0
 
LVL 15

Expert Comment

by:efn
ID: 9765757
>  So basically the compiler should be ignoring that operator all together as I use the CBuffer as a protected subclass shouldn't it?

No, protected inheritance means that clients of CString can't see CBuffer's members, but CString can still see all of CBuffer's public and protected members.

Why don't you just get rid of the operator [] function?  You can still subscript the pointer returned by operator T* and get the same effect.

--efn
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9765894
> Why don't you just get rid of the operator [] function?  You can still subscript the pointer returned by operator T* and get the same effect.

This would indeed solve the problem. However I cannot put error checking in the code that way. It's something I could live with though. BUT, next problem arises in this case, which is actually just the same problem (or atleast one of a kind):

I have this code:

template <typename T>
class CBuffer
{
public:
    operator T * () const throw()
    {
        return m_buffer;
    }

private:
    T * m_buffer;
};

class CString : protected CBuffer<TCHAR>
{
public:
    operator LPCTSTR() const throw()
    {
        return (LPTSTR)(*this);
    }

    void Empty() throw()
    {
        (*this)[0] = '\0';
    }
};

And get:

c:\\Weltevreden Framework\Source\Utility.hpp(482) : error C2593: 'operator [' is ambiguous
could be 'built-in C++ operator[(TCHAR , int)'
or       'built-in C++ operator[(LPCTSTR, int)'
while trying to match the argument list '(Weltevreden::Utility::CString, int)'

Also tried:

template <typename T>
class CBuffer
{
public:
    operator T * () const throw()
    {
        return m_buffer;
    }

    operator const T * () const throw()
    {
        return m_buffer;
    }

private:
    T * m_buffer;
};

class CString : protected CBuffer<TCHAR>
{
public:
    using CBuffer<TCHAR>::operator LPCTSTR;

    void Empty() throw()
    {
        (*this)[0] = '\0';
    }
};

But this gives the same results. Why do I want CBuffer to have a pointer to a non const object? Well because I want to be able to pass it to any function as a parameter. All it does is to provide some usefull memory allocation functions, but basically it just functions as a normal pointer. I'm open for more of this kind of suggestions though, I didn't even think of efn's solution :-)
0
 
LVL 19

Expert Comment

by:Dexstar
ID: 9765968
It basically goes back to what I said originally:  Both of those operators are so similiar, the compiler can't tell them apart.  You don't need both, so get rid of one or the other, and you should be fine.

I had classes that had both operators too, and when I switch to VS.NET, the compiler started to complain about them, so I removed the T* operator, and haven't had any issues.

But, it doesn't matter which one you get rid of, you basically have the same function twice.

Dex*
0
 
LVL 15

Expert Comment

by:efn
ID: 9766134
The compiler is right that your code is ambiguous.  You have two functions that can generate a pointer that can be subscripted.  The compiler doesn't have to be smart enough to see that the expression is on the left side of an assignment expression, so you probably want the one that points to non-constant characters.

You could specify unambiguously which function you want:

void Empty() throw()
{
    operator TCHAR*()[0] = '\0';
}

--efn
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9766206
>> operator TCHAR*()[0] = '\0';

This solution would undermine my attempt to make it more readable.

>> No, protected inheritance means that clients of CString can't see CBuffer's members, but CString can still see all of CBuffer's public and protected members.

Does this mean that the above code wont compile, but this code will:

CString strString;
strString[0] = '\0';

I guess it does, makes sense too... :-) I think this is an interesting problem. It's one of those things were you have to choose between readability and 'workability'. The problem here is that I want CBuffer to use the non-const version of the operator, but CString to use the const version of the operator (for making it more safe). The easy solution would be to just use a non-const operator for CString as well and assume the programmer will not make the mistake of passing it to a function which might modify the buffer. Or I might make a const operator for CBuffer and give it a getPointer() function which returns a non const pointer. I guess I will do something like that.

I still find this an interesting question though so before I accept any answers I will wait a bit more for some constructive imput on how to work with such issues.
0
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 
LVL 19

Expert Comment

by:Dexstar
ID: 9766269
Keep in mind:

You can have BOTH a const and a non-const version.  The compiler (should) take the const version whenever it doesn't need a non-const.  Different situations will call for different things, and if you're making this class to use in the future, then you should plan ahead.  :)

D*
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9766505
But if I have both a const and non-const version it will complain about an ambigious operator everytime I try to cast it to a non const parameter wont it? Why else would it complain now?
0
 
LVL 19

Expert Comment

by:Dexstar
ID: 9766520
It shouldn't complain.

It is complaining now because you have 2 different non-const that both mean the same thing.

Dex*
0
 
LVL 15

Expert Comment

by:efn
ID: 9766544
> Does this mean that the above code wont compile, but this code will:

> CString strString;
> strString[0] = '\0';

> I guess it does, makes sense too...

No, I don't think this should compile.  If it's inside the class, operator [] is ambiguous; if it's outside the class, it can't use operator LPCTSTR because that is a pointer to const characters, and it can't use operator TCHAR* because that function is inaccessible due to the protected inheritance.

>  I want CBuffer to use the non-const version of the operator, but CString to use the const version of the operator (for making it more safe).

I think that's what you had in the last sample, if you can bring yourself to disambiguate the Empty code.

> ...assume the programmer will not make the mistake of passing it to a function which might modify the buffer.

Danger!

--efn
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9766807
I see where this is going now indeed.

If I use this code:

template <typename T>
class CBuffer
{
public:
    operator T * () const throw()
    {
        return m_buffer;
    }

private:
    T * m_buffer;
};

class CString : protected CBuffer<TCHAR>
{
public:
    operator LPCTSTR() throw()
    {
        return (LPTSTR)(*this);
    }

    void Empty() throw()
    {
        (LPTSTR)(*this)[0] = '\0';
    }
};

Should do the trick then right? I can use the accessor inside the class by casting it to LPTSTR first and I can use it outside of the class without any special effort. I'll test it as soon as I'm able to :-)

>> No, I don't think this should compile.  If it's inside the class, operator [] is ambiguous; if it's outside the class, it can't use operator LPCTSTR because that is a pointer to const characters, and it can't use operator TCHAR* because that function is inaccessible due to the protected inheritance.

You are right, I ment in that case with a overloaded type casting operator too.
0
 
LVL 19

Assisted Solution

by:Dexstar
Dexstar earned 125 total points
ID: 9766821
Yeah, there you go!  That should work!

Dex*
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9767351
Yes it should, but it doesn't...

template <typename T>
class CBuffer
{
public:
    operator T * () const throw()
    {
        return m_buffer;
    }

private:
    T * m_buffer;
};

class CString : protected CBuffer<TCHAR>
{
public:
    operator LPCTSTR() throw()
    {
        return (LPTSTR)(*this);
    }

    void Empty() throw()
    {
        ((LPTSTR)(*this))[0] = '\0';
    }
};

This part works fine, but if you compile this:

void main()
{
    CString strString;

    strString[0] = 'T';
    strString[1] = 'e';
    strString[2] = 's';
    strString[3] = 't';
    strString[4] = '\0';

    MessageBox(NULL, strString, NULL, 0);
}

It goes wrong. Even outside the class it still looks at the protected operator from CBuffer. I get the following error:

c:\Weltevreden Framework\Test Project\Main.cpp(10) : error C2593: 'operator [' is ambiguous
could be 'built-in C++ operator[(TCHAR , int)'
or       'built-in C++ operator[(LPCTSTR, int)'
while trying to match the argument list '(Weltevreden::Utility::CString, int)'

from which the first alternative is the CBuffer operator (I assume). So what's this? Desired compiler behaviour or not? So you guys have any more ideas? I -could- drop the operator from the CBuffer class and change it for a GetPointer() function, this would solve the problem, BUT I can't use a CBuffer type variable as a parameter anymore (which probably won't happen much anyway). And something I'd also still like is to be able to do some error checking on the [] operator.
0
 
LVL 15

Accepted Solution

by:
efn earned 125 total points
ID: 9768248
> So you guys have any more ideas?

Of course!  Since you can't use a CString as a CBuffer anyway because the inheritance is protected, you could just use composition instead of inheritance:

class CString
{
public:
      operator LPTSTR() throw()
      {
            return buffy;
      }

      operator LPCTSTR() throw()
      {
            return buffy;
      }

      TCHAR& operator[](int sub)
      {
            return buffy[sub];
      }
   
      const TCHAR& operator[](int sub) const
      {
            return buffy[sub];
      }

      void Empty() throw()
      {
            buffy[0]= '\0';
      }
private:
      CBuffer<TCHAR> buffy;
};

This compiles, runs, is readable, lets you add checking in the operator [] functions, lets you pass a CString to a function expecting a pointer, and lets you read and write individual characters.  What more could you ask?  Inheritance, I guess, if you are attached to using inheritance for some reason.

--efn
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 9768355
MFC's CString class does not allow this syntax:
   strString[1]= 'T';
Maybe there is a reason :)

I've never worked on this type of problem before, but I found that I stopped getting errors when I added this to CBuffer:
      T& ElementAt( int nIdx ) { return m_buffer[nIdx]; }
and this to CString:
      TCHAR& operator[](int nIndex) { return CBuffer<TCHAR>::ElementAt(nIndex); }

But I don't really know why.  
I examined the MFC CObArray code for guidance.  The programmers' at Microsoft have encountered -- and solved -- all of these issues, many times, many years ago.

-- Dan
0
 
LVL 1

Author Comment

by:LuCkY
ID: 9769928
Actually I started using CBuffer as a member variable, but I started thinking that it would be a lot easier just to implement it to my class so I can use a lot of the base functions. For instance when the copy constructor gets called, all I have to do is to call the copy constructor of CBuffer on the initialization list.

>> I examined the MFC CObArray code for guidance.  The programmers' at Microsoft have encountered -- and solved -- all of these issues, many times, many years ago.

Yes I know I'm reinventing the wheel bigtime :-) Though sometimes if I look at the source code of Microsoft I really wonder why they took a certain approach.

Anyway I think this has lasted long enough now and I got a few nice idea's to work with. I guess I'll use it as a member variable again. Thanks a lot for all your help and usefull ideas :-)
0
 
LVL 19

Expert Comment

by:Dexstar
ID: 9771893
Dan:

The reason CString doesn't allow direct access to the buffer (without the use of GetBuffer) is because it implements reference counting, and you can't/shouldn't change the buffer without it making a lazy copy first.

Dex*
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Unlike C#, C++ doesn't have native support for sealing classes (so they cannot be sub-classed). At the cost of a virtual base class pointer it is possible to implement a pseudo sealing mechanism The trick is to virtually inherit from a base class…
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…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.

758 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

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now