Want to win a PS4? Go Premium and enter to win our High-Tech Treats giveaway. Enter to Win

x
?
Solved

Small yet annoying (template) overloading problem

Posted on 2003-11-17
21
Medium Priority
?
464 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
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
 
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 500 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 500 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

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

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

C++ Properties One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property (http://www.experts-exchange.com/Programming/Languages/CPP/A_3912-Object-Properties-in-C.ht…
Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.
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.

610 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