ADL in case of equal-named member function
Asked Answered
B

3

8

The situation is that some member function bar::Bar::frobnicate wants to utilize ADL to find a function from some unknown namespace, within a function that has an identical name. However, it only finds its own name.

Testcase

(Note that in reality, Bar is a Foo-agnostic template; this is just the reproducible, minimal testcase)

namespace foo {
    struct Foo {};
    void frobnicate(Foo const &) {}
}

namespace bar {
    struct Bar {
        void frobnicate() {
            foo::Foo foo;
            frobnicate(foo); // <-- error
        }
    };
}

int main () {
    bar::Bar x;
    x.frobnicate();
    frobnicate(foo::Foo());
}

Results in:

test.cc: In member function ‘void bar::Bar::frobnicate()’:
test.cc:10:31: error: no matching function for call to ‘bar::Bar::frobnicate(foo::Foo&)’
test.cc:10:31: note: candidate is:
test.cc:8:18: note: void bar::Bar::frobnicate()
test.cc:8:18: note:   candidate expects 0 arguments, 1 provided

Standard

I understand that this is correct compiler behaviour:

3.4.1 Unqualified name lookup [basic.lookup.unqual]

(...) name lookup ends as soon as a declaration is found for the name (...)

and only after unqualified lookup failed, argument dependent lookup comes into play:

3.4.2 Argument-dependent name lookup [basic.lookup.argdep]

When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched

Workaround

My current workaround is to introduce a special traits class that does not define the clashing name itself:

    struct BarTraits {
        void frobnicate_(foo::Foo const &b) {
            frobnicate(b);
        }
    };

or this ligher version:

    void frobnicate_(foo::Foo const &c) { frobnicate(c); }

Question

Are there better alternatives than introducing such traits classes?

Explicitly qualifying the call as foo::frobnicate(foo) is not an option here, because (as mentioned) the Bar class is a template upon Foo in reality and should not only work for types in the foo namespace.

Benedicto answered 24/7, 2013 at 6:1 Comment(1)
+1 for making me lookup ADL, I learnt something new today!Arlaarlan
A
2

As you found out yourself, adding a member function frobnicate to the class interface of Bar (or Bar<T> in the template case), will prevent ADL from finding foo::frobnicate.

The easiest -and in this case idiomatic- way to add frobnicate functionality to a class Bar (or to a class template Bar<T>) is to add a non-member function frobnicate(Bar) (or function template frobnicate(Bar<T>)) to the namespace bar

namespace foo {
    struct Foo {};
    void frobnicate(Foo const &)  {}
}

namespace bar {
    template<class T>
    struct Bar {    
       T t;    
    }; 

    template<class T>
    void frobnicate(Bar<T> const& b)
    {
        frobnicate(b.t);    
    }
}

int main () {
    bar::Bar<foo::Foo> x;
    frobnicate(x);
    frobnicate(foo::Foo());
}

If you insist on having a member function, you will have to rename it to something like do_frobnicate(). I would not use type traits tricks to get the same behavior as it is an indirect approach, and makes the class interfaces much harder to understand (remember Stroustrup's motto: "represent your ideas directly in code").

Accelerate answered 24/7, 2013 at 6:32 Comment(11)
The thing with the member function is that I wanted my Bar to also satisfy some abstract interface (it's declaration looks like template<typename T, typename Base> class Bar, and it is basically a container of T). I think it would indeed be cleanest to not implement that abstract interface and have a free function, and then reuse Bar as a member in something that implements Base, but I have to think about it a bit more. +1 so far.Benedicto
@phresnel It is indeed important to consider that type of interface Bar is supposed to have. It is also important to consider whether foo and bar are related enough to be merged. See e.g. this old Sutter column What's in a class.Accelerate
To give more insight: The real situation is a Ray Tracer. I have shapes and primitives, where a shape is just a shape, and a primitive is a bit like a shape, but adds materials. The Bar in fact is a "Bounding Interval Hierarchy" (a tree structure). To avoid code duplication, the BIH is generic enough to be able to be a shape, or a primitive, or even something else. That is, foo and bar are unfortunately not related anough, foo can actually be a Sphere, a Triangle, or even another, nested BIH. I think, currently the cleanest way is to provide a sort of container adapters, which ...Benedicto
... implement a Shape interface and a Primitve interface for my generic BIH, so I would not be using the BIH directly, and just accept that my BIH-implementation is just a toolbox to implement real containers on it.Benedicto
@phresnel adding "outside" frobnicate functionality to such a complicated class hierarchy really is best done through non-member functions, in order not further increase the classes responsibility. It's also the way the Standard Library works: containers that provide a concise storage interface, and a whole bunch of algorithms that operate on such classes through iterators.Accelerate
@TemplateRex: +1 for the nice answer; however, I've a doubt. If I'm, like the OP, creating Foo on the fly and have no requirement to store a Foo as Bar's member, I can do away with the template right? I tried it by creating Foo inside bar::frobnicate and it seems to work without the template.Arlaarlan
@Arlaarlan sure, if you only ever use temporary foo::Foo objects, there is no need to have an extra frobnicate in namespace bar. The answer shows how to call it on Foo objects inside another class.Accelerate
@TemplateRex: Aah, I see your point. The only downside here would be that the class' data member should be public for the non-member function to access it, but I see that having it as a member function will make ADL not work, so this should be a happy medium which became an idiom.Arlaarlan
@Arlaarlan the public data member was just for illustration purposes. in production code, it could be hidden behind a getter()Accelerate
@TemplateRex: True, the usual way of exposing data through an interface instead of the actual memberArlaarlan
In the case of a class, a friend statement may become necessary.Cymbal
G
1

you can use this trick

namespace dummy { void your_func(may be some parameteres); }
struct bar { 
   void member() {
      using dummy::your_func; // now your call will find that and ADL will kick in
Giraud answered 27/9, 2013 at 9:53 Comment(0)
C
0

I just do it with a proxy function

namespace utils 
{
int lookup_adl(int arg) 
{
  return lookup(arg);// ADL search
}
struct Foo 
{
  int lookup(int arg) { return ::utils::lookup_adl(arg);}
};
}
Coessential answered 28/4, 2020 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.