5

I faced a compilation problem that I do not understand, I have simplified it a bit for explanation below.

Basically, it involves having 2 different getters (a const and non-const one) that return a container (a map in this example) with const, respectively non-const value_type.

What puzzles me is that in the example below, the compiler seems unable to use the const getter on a non-const object:

#include "stdafx.h"
#include <utility>
#include <map>

class TestObject
{
public:

    TestObject() {}
    virtual ~TestObject() {}
};

typedef std::pair<const TestObject*, const TestObject*> ConstTestObjectPair;
typedef std::pair<TestObject*, TestObject*> TestObjectPair;

class Test
{
    TestObject* m_pObject;

public:

    Test() {m_pObject = new TestObject();}
    virtual ~Test() {delete m_pObject;}

    std::map<unsigned, ConstTestObjectPair> GetObject() const
    {
        std::map<unsigned, ConstTestObjectPair> map;
        map.insert(std::make_pair(0, std::make_pair(m_pObject, m_pObject)));
        return map;
    }

    std::map<unsigned, TestObjectPair> GetObject()
    {
        std::map<unsigned, TestObjectPair> map;
        map.insert(std::make_pair(0, std::make_pair(m_pObject, m_pObject)));
        return map;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Test* pTest = new Test();
    const Test* pConstTest = pTest;

    std::map<unsigned, ConstTestObjectPair> CTO = pTest->GetObject(); // Not compiling, I don't get why!!!
    CTO = pConstTest->GetObject();

    std::map<unsigned, TestObjectPair> TO = pTest->GetObject();
    //TO = pConstTest->GetObject(); // Not working, this is expected

    return 0;
}

I tried with both VS2010 and gcc and neither accepts to compile this code. Here is the compilation error returned by VS2010:

1>c:\test.cpp(48): error C2440: 'initializing' : cannot convert from 'std::map<_Kty,_Ty>' to 'std::map<_Kty,_Ty>'
1>          with
1>          [
1>              _Kty=unsigned int,
1>              _Ty=TestObjectPair
1>          ]
1>          and
1>          [
1>              _Kty=unsigned int,
1>              _Ty=ConstTestObjectPair
1>          ]
1>          No constructor could take the source type, or constructor overload resolution was ambiguous

Could someone explain me why the compiler cannot find/use the correct prototype on the non-const object?

Thanks a lot!

Duduche
  • 53
  • 6
  • FWIW gcc complains at the same point with: No viable conversion from 'map<[...], pair>' to 'map<[...], pair>' – Nicholaz Jun 04 '13 at 16:07

4 Answers4

5

If you're really curious, check out section 13.3.3 of the C++03 standard, which describes how the "best viable function" is determined. Here are some important points:

The selection criteria for the best function are the number of arguments, how well the arguments match the types of the parameters of the candidate function, how well (for nonstatic member functions) the object matches the implied object parameter, and certain other properties of the candidate function. [Note: the function selected by overload resolution is not guaranteed to be appropriate for the context. Other restrictions, such as the accessibility of the function, can make its use in the calling context ill-formed. ]

And later:

If there is exactly one viable function that is a better function than all other viable functions, then it is the one selected by overload resolution

Note that the return type of the function is not mentioned in this criteria. So the non-const method is selected as most valid, because its "implied object parameter" (essentially the "this" pointer) is non-const. This all happens before the conflict with the return type is detected.

To solve this problem, I would either:

  • Change your design so that ConstTestObjectPair isn't needed, and you can just use const TestObjectPair (preferred solution)
  • Cast your non-const objects to const ones when needed
Taylor Brandstetter
  • 3,523
  • 15
  • 24
  • Thanks for the pointer to the C++ standard, I picked this answer as it was the most comprehensive and had solution suggestions. – Duduche Jun 05 '13 at 06:02
  • @Duduche Unfortunaltely, this answer is wrong. The **non-const** method is called. And why is the above citation of the standard crucial? Which method do you expect the compiler will call if not the best match? The crucial point is, how to determine what the best viable function is. – MWid Jun 05 '13 at 09:36
  • Oh that was just a typo, fix'd. And I added another citation which should make it more clear. – Taylor Brandstetter Jun 05 '13 at 13:41
4

What puzzles me is that in the example below, the compiler seems unable to use the const getter on a non-const object

It is not "unable" but required to chose the other one.

Overloading is selected using the passed actual parameters. For member function including the hidden param used for this. For T* the non-const overload is selected, if you want the other you must use a const T* by cast or other means.

Actually it is a common mistake to think the return type will be used some way and the function that returns what you want to use in the expression gets selected. It is just not so.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37
1

The problem is quite simple. pTest is a pointer to an object of type Test, which is not const. Hence in the call pTest->GetObject() the non-const member function is selected, that is

std::map<unsigned, TestObjectPair> GetObject()

As you see, this function returns a value of type std::map<unsigned, TestObjectPair>. But then you try to initialize the variable CTO of type

std::map<unsigned, ConstTestObjectPair>

with this value. To do that, the compiler needs to convert the returned value to this type. But there is no conversion constructor to do that. And that is what the compiler error tells you.

MWid
  • 4,429
  • 3
  • 20
  • 20
0

C++ compiler will choose the explicit overrided method, so here pTest is a non-const viable and pConstTest is const one.

Test* pTest = new Test();
const Test* pConstTest = pTest;

pTest->GetObject will choose the non-const GetObject:

std::map<unsigned, TestObjectPair> GetObject()
{
    std::map<unsigned, TestObjectPair> map;
    map.insert(std::make_pair(0, std::make_pair(m_pObject, m_pObject)));
    return map;
}

pConstTest->GetObject() will choose the const GetObject:

std::map<unsigned, ConstTestObjectPair> GetObject() const
{
    std::map<unsigned, ConstTestObjectPair> map;
    map.insert(std::make_pair(0, std::make_pair(m_pObject, m_pObject)));
    return map;
}

the first error happened when you assign a returned

std::map<unsigned, TestObjectPair> value 

to a

std::map<unsigned, ConstTestObjectPair> viable
diwatu
  • 5,641
  • 5
  • 38
  • 61