DimG DimG - 1 month ago 8
C++ Question

Instantiation of a hidden function

Condider the following code:

template <class Impl, class Cont>
struct CrtpBase
{
void foo()
{
cout << "Default foo\n";
Cont cont;
cont.push_back(10); // Going to fail if Cont::push_back doesn't exist
}
};

typedef std::unordered_map<int,int> ContType;
struct Child : public CrtpBase<Child, ContType>
{
typedef CrtpBase<Child, ContType> _Parent;
// using _Parent::foo // (1)
void foo() { cout << "Child\n"; }
};

int main()
{
Child obj;
obj.foo(); // (2)
return 0;
}


What I'm stuck at is conditions when CrtpBase class is instantiated and when it isn't.

At point (2), when I call
foo()
, to my point of view, the compiler should generate a list of possible overloads. These are to be
Child::foo()
and
Child::_Parent::foo()
. Therefore
Child::_Parent::foo()
has to be instantiated. (At this point compilation should fail since the error is in the body function, SFINAE not applicable) Then the compiler should choose the priority match.

However the program compiles and shows that
CrtpBase::foo
is not instantiated. The question is why. The compiler somehow knows that
Child::foo
would be the best match and stops overload resolution process.

When I uncomment
// using ...
, asking compiler explicitly to use base function overload to be one of the matches, nothing changes, it compiles again

Just to make a sort of conclusion of what I have understood from the answers below as they cover different aspects of what's going on


  1. The compiler doesn't even consider the base overload. That is the crucial fact that I didn't see, hence all the misunderstanding

  2. That is not the case here, but choosing the best match during the overload resolution instantiates only the chosen match


Answer

At point (2), when I call foo(), to my point of view, the compiler should generate a list of possible overloads.

Trueish. We start by looking up the name foo in Child. We find Child::foo and then stop. We only have one candidate, which is a viable candidate, so we call it. We don't continue to look in the base classes, so CrtpBase::foo is never considered. This is true regardless of signatures (if CrtpBase::foo() took an int, obj.foo(4) would fail to compile because Child::foo() doesn't take an argument - even if a hypothetical call to CrtpBase::foo(4) would be well formed).

When I uncomment // using ..., asking compiler explicitly to use base function overload to be one of the matches, nothing changes, it compiles again

That's actually not what you're doing. The using-declaration brings the name CrtpBase::foo into the scope of Child, as if it were declared there. But then the declaration of void foo() hides that overload (otherwise the call would be ambiguous). So again, name lookup for foo in Child finds Child::foo and stops. Where this case differs from the previous case is if CrtpBase::foo() took different arguments, then it would be considered (such as my hyptohetical obj.foo(4) call in the previous paragraph).

Comments