3

I need to call init(int* iNumber) function which is derived from the base class.

BaseClass.h

#pragma once
#include "stdafx.h"
template <class T>
class BaseClass
{
public:
    BaseClass() {}
    virtual ~BaseClass() {}
    virtual void init(T* object) = 0;
};

ChildClass.h

#pragma once
#include "BaseClass.h"

class ChildClass : public BaseClass<int>, public BaseClass<float>
{
public:
    ChildClass() {}
    virtual ~ChildClass() {}
};

ChildClassImpl.h

#pragma once
#include "ChildClass.h"
class ChildClassImpl : public ChildClass
{
public:
    ChildClassImpl();
    virtual ~ChildClassImpl();
private:
    void init(int* iNumber) override;
    void init(float* fNumber) override;
};

ChildClassImpl.cpp

#include "stdafx.h"
#include <iostream>
#include "ChildClassImpl.h"

ChildClassImpl::ChildClassImpl(){}

ChildClassImpl::~ChildClassImpl(){}

void ChildClassImpl::init(int* iNumber)
{
    std::cout << "Integer constructor: " << *iNumber << std::endl;
}

void ChildClassImpl::init(float* fNumber)
{
    std::cout << "Float constructor: " << *fNumber << std::endl;
}

MainClass

#include "stdafx.h"
#include <iostream>
#include "ChildClassImpl.h"

using namespace std;

int main()
{
    ChildClass* childClass = new ChildClassImpl();
    int x = 10;
    childClass->init(&x);
    cout << "Test" << endl;
    getchar();
    return 0;
}

At compile time this is gives the error

Severity  Code    Description Project File    Line Error
(active)      "BaseClass<T>::init [with T=int]" is
ambiguous ConsoleApplication4 d:\Learning\ConsoleApplication4\ConsoleApplication4\ConsoleApplication4.cpp 14

What am I doing wrong here? How could I fix it with minimal changes?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 3
    Put `using BaseClass::init;using BaseClass::init;` somewhere in the declaration of `ChildClass` – Piotr Skotnicki Dec 12 '17 at 10:23
  • 3
    The output you write from the `ChildClassImpl::init` functions tells me that you should not have those functions. Instead the functionality of them should be put into the *actual* constructor. – Some programmer dude Dec 12 '17 at 10:23
  • 1
    @SPD: To be fair, it is a small example that demonstrates the issue, not an advert for good program design. – Gem Taylor Dec 12 '17 at 10:43
  • Thank you @PiotrSkotnicki. It did work. It would be much nicer if you could explain why does that happens. Why the compiler can't figure-out the correct base class? – Ravindu Kumarasiri Dec 12 '17 at 11:18
  • It is a weakness that the resolution rules for pre-template C++ basically say the base methods are automatically promoted/conflicted only on name, and don't consider the arguments. The benefit is that this allows the derived class to shield both methods with another signature. Manually promoting both methods with `using` allows the proper overload resolution to kick in, but isn't a help if you have many methods with this behaviour. Your example shows a template usage I like, but does not fit the language before, when the two base classes would have had to be explicitly named. – Gem Taylor Dec 12 '17 at 11:33
  • [Of course in the case where 2 mid classes derive from a identical base overload resolution can't work either] Is here any sign of an RFC to improve this? I.e. keep the old shielding, but allow argument overload resolution when possible? We do it for namespaces (without the shielding)! – Gem Taylor Dec 12 '17 at 11:33

1 Answers1

0

This code fails because C++ performs name lookup before overload resolution and access control check. That is first step would be to determine to which class scope init belongs to. And in this case result would be ambiguous because init could refer to either BaseClass<int>::init or BaseClass<float>::init. Introducing an extra using declaration will bring both of those functions into ChildClass scope:

class ChildClass : public BaseClass<int>, public BaseClass<float>
{
public: using BaseClass<int>::init;
public: using BaseClass<float>::init;

So name lookup will determine that init refers to ChildClass::init and compiler will proceed to overload resolution.

Alternatively you can perform a cast (which is definitely not as convenient):

static_cast<BaseClass<int> *>(childClass)->init(&x);
user7860670
  • 35,849
  • 4
  • 58
  • 84