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?
In the C++ programming language, argument-dependent lookup (ADL), or argument-dependent name lookup, applies to the lookup of an unqualified function name depending on the types of the arguments given to the function call.
It lets you store function pointers, lambdas, or classes with operator() . It will do conversion of compatible types (so std::function<double(double)> will take int(int) callable things) but that is secondary to its primary purpose.
(since C++11) Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.
std::function is a type erasure object. That means it erases the details of how some operations happen, and provides a uniform run time interface to them. For std::function , the primary1 operations are copy/move, destruction, and 'invocation' with operator() -- the 'function like call operator'.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With