ADL with std::function: Can functions taking std::function objects be found via the types in the std::function's argument list?
Asked Answered
O

1

6

Consider the following code snippet:

#include <functional>

namespace ns {
    struct Arg{};
    using Func = std::function<int(Arg)>;

    Func operator+(Func lhs, Func rhs) {
        return [lhs, rhs](Arg arg) {
            return lhs(arg) + rhs(arg);
        };
    }
}

int main() {
    ns::Func foo = [](ns::Arg i) {return 5;};
    ns::Func bar = [](ns::Arg i) {return 2;};
    auto foobar = foo + bar;
    return foobar(ns::Arg());
}

The above code compiles with various compilers. In contrast, the following code snippet does not compile. The only difference is the type of the argument used inside Func (Arg vs int):

#include <functional>

namespace ns {
    using Func = std::function<int(int)>;

    Func operator+(Func lhs, Func rhs) {
        return [lhs, rhs](int i) {
            return lhs(i) + rhs(i);
        };
    }
}

int main() {
  ns::Func foo = [](int i) {return i + 5;};
  ns::Func bar = [](int i) {return i * 2;};
  auto foobar = foo + bar; // BANG! Error here!
  return foobar(2);
}

I understand the error of the latter version: The called operator+ is defined in a namespace and thus not found without explicit specification of the namespace. Argument dependent lookup will not help here, because the operator+ is defined in a different namespace (ns) than the type of the argument (std::function is defined in namespace std, the using declaration is irrelevant for this).

But why is the correct operator+ found in the case where Func takes an argument ns::Arg? The namespace of Func has not changed. Is the code using Arg valid according to the C++ standard?

Oversold answered 14/8, 2019 at 14:17 Comment(0)
K
7

Is the code using Arg valid according to the C++ standard?

It is. The associated namespaces for ADL include the associated namespaces of any template argument for a specialization, according to [basic.lookup.argdep/2.2]

... Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces. — end note ]

std::function<int(Arg)> is a class template specialization, and ns is associated with one of its arguments. Therefore ns is included in the set of namespaces that is searched for operator+ by ADL.

This rule exists to make reusable components more useful. The idea is to allow a library to expose say an API that takes a std::unique_ptr<ns::Foo> as a handle type, and for ADL to pick up the correct functions from ns when presented with a handle.

Kurdish answered 14/8, 2019 at 14:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.