Search code examples
c++name-lookup

Function falls completely off the candidate list if it takes a pointer to the class


Why can't I have a non-member function with the same name as a member function, if it also happens to take a pointer to the class?

This doesn't compile:

struct FooConfig{ int value; };
struct BarConfig{ int value; };

class Api
{
    void configure(const FooConfig& cfg);
    void configure(const BarConfig& cfg);
};

// helper
static void configure(Api* self, int value)
{
    // Actual impl
}

void Api::configure(const FooConfig& cfg)
{
    configure(this, cfg.value);
}

void Api::configure(const BarConfig& cfg)
{
    configure(this, cfg.value);
}

Both Gcc and Clang are lying to me, saying that there is no function named configure that takes 2 arguments.

And here comes the funny part:

If I just rename the helper function with the self pointer, or make it take a reference instead of pointer, then it suddenly comes to existence, and all is well.

AFAIK, the coincidental resemblence to a member function isn't supposed to matter in name lookup. Am I fooling C++?


Solution

  • Before overload resolution selects the best viable function, a set of candidate functions is formed, specifically matching the name of the callee. For that, the name lookup procedure is used.

    Expression configure(this, cfg.value) is an unqualified call inside a class member function scope, and for that, the name lookup procedure will first search for the declaration in that function block itself (before the first use), and if not found, then traverse the class and its base classes, and only if still not found, it will visit the enclosing namespaces. That is, it will stop at the first declaration when using this ordered hierarchy of searched scopes.

    In phase 2 of the above procedure, the name loopkup procedure finds both overalods of Api::configure, and that forms the candidate set for overload resolution. Since neither takes two parameters, the compiler correctly diagnoses an error.

    In order to force the usage of the function from the global namespace, use ::configure(this, cfg.value). This indicates which namespace should be searched for for the declaration of configure.

    If I [...] make it take a reference instead of pointer, then it suddenly comes to existence, and all is well.

    That is not possible, as this does not impact the name lookup procedure. Changing the name, however, does.