Workaround for the inverse of Argument Dependent Lookup?

523 views Asked by At

C++ has ADL (Argument Dependent Lookup) by which, as its name describes, the context (namespace) of a function can be implied from the context (namespace) of (any of) the argument(s).

fun(a); // if the type of a is in namespace ns deduce ns::f if available

My question is if the reverse is also possible by some technique? By reverse I mean if the context (namespace) can be deduced from the context of the called function. Some sort of "Function Dependent Lookup" (FDL). Fake code:

ns::fun(a); // deduce ns::a if available

I can't figure out a way of doing that. This limitation is particularly annoying for enums used to encode functions options. I would like to know if there is a technique to simulate this feature (C++11 would be ok too). Fake code:

ns::fun(Saturday, Tuesday); // Saturday/Tuesday are enum values in namespace ns;

Especially if there is a workaround for enums.

This code illustrates the issue:

namespace longname{
    class A{};
    void fun(A const& a){}
    A global_a;

    enum Days { Saturday,Sunday,Tuesday,Wednesday,Thursday,Friday};
    void gun(Days d1, Days d2){}    
}

int main(){
    longname::A a;
    fun(a); // cool, longname::fun(a) not necessary, fun is deduced from context

    longname::fun(global_a); // error, not cool, global_a context not deduced, 
    // must use then longname::fun(longname::global_a)
    longname::gun(Saturday, Tuesday); // error, particularly not cool, the Saturday is not deduced from context 
    // must use then longname::gun(longname::Saturday, longname::Tuesday)
    // or at best gun(longname::Saturday, longname::Tuesday)
}

EDIT: @jrok suggested a workaround based on defining nested namespace. For the enum case, I get this code. Which still has some noise (there is really no "dependent" lookup at all) but it is an improvement.

namespace longname{
    namespace days{
        enum _ { Saturday,Sunday,Tuesday,Wednesday,Thursday,Friday};
    }
    void gun(days::_ d1, days::_ d2){}  
}

int main(){
    using namespace longname::days; // some noise still here
    longname::gun(Saturday, Tuesday);
}

I am not using enum class because then Saturday, Sunday, etc cannot be brough directly in scope (in fact using longname::days::_ would give me a compile error)

1

There are 1 answers

0
user3125280 On BEST ANSWER

Yes and no. Mostly no.

The bad news is if an enum is outside the current scope, such as Tuesday, etc then it can't be passed to a function, even if that function was declared in a namespace where the enum was visible. This is because argument lookup occurs first when you write a function call and the arguments can not be passed to gun and then have name lookup occur. Nothing can change this - however there is good news too.

Firstly you seem to need behaviour that maps ns::foo(arg1, arg2) -> {using namespace ns; ns::foo(arg1, arg2);}. Function calls and templates can't change this but macros kind of can and I included and example.

Also I gave a basic example of argument dependent lookup. You can see that the out-of-scope functions GetMonday and GetTuesday (which return your out-of-scope enum) can be found using this mechanism simply because you included one type from that namespace. RegisterNamespace::val adds the hidden namespace to the scope when the compiler is trying to find GetMonday, and GetMonday returns a Days which allows the compiler to find foo.

Really you want the compiler to alter the scope by adding additional namespaces when it encounters a function from another namespace. However the compiler has already determined the types of the arguments by then, and actually needs them to work out other possible alternatives to the function.

#include <iostream>

namespace hidden {

enum RegisterNamespace { val };

enum Days {
    Monday,
    Tuesday
};

void foo(Days a , Days b){std::cout << "Called foo\n";}

Days GetMonday(RegisterNamespace a = val){return Days::Monday;}
Days GetTuesday(RegisterNamespace b = val){return Days::Tuesday;}

}

using namespace std;

#define UseNamespace(ns, x) do {using namespace ns; x;} while (0)

int main()
{
    //with a macro
    UseNamespace(hidden,hidden::foo(Monday, Tuesday));

    {
    //foo is found by argument dependent lookup
    using hidden::Days;
    foo(Days::Monday,Days::Tuesday);
    }

    {
    using r = hidden::RegisterNamespace;
    //foo and GetMonday / GetTuesday are all found by argument dependent lookup
    foo(GetMonday(r::val),GetTuesday(r::val));
    }

    return 0;
}