How to make these std::function parameters unambig

2019-05-16 08:51发布

问题:

The following function overloads are ambiguous when passing a lambda. I found out that std::function can be constructed from most callable types, even if their signature does not match. So the compiler can't tell which function to use.

template <typename T> void each(std::function<void(T)> iterator);
template <typename T> void each(std::function<void(T, id)> iterator);
template <typename T> void each(std::function<void(T&)> iterator);
template <typename T> void each(std::function<void(T&, id)> iterator);

There are some similar questions out here, but none of them could solve my problem. How can I resolve the ambiguity without changing the usage? Morever, at the time I have to explicitly mention the template type. Is there a way around this?

回答1:

One half of this is LWG issue 2132, removing std::function's constructor from overload resolution unless the argument is actually callable for the argument types specified. This requires expression SFINAE support to implement, which VC++ doesn't have.

The other half of the issue is overload resolution:

#include<functional>
#include<iostream>
struct id {};
template <typename T> void each(std::function<void(T)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <typename T> void each(std::function<void(T, id)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <typename T> void each(std::function<void(T&)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
template <typename T> void each(std::function<void(T&, id)> ){ std::cout << __PRETTY_FUNCTION__ << std::endl; }
int main() {
    each<int>([](int, id){});
}

With a library that implements LWG2132, this code prints, perhaps surprisingly:

void each(std::function<void(T&, id)>) [with T = int]

Why? First, it is possible to construct a std::function<void(T&, id)> from [](int, id){}. After all, the latter can be called with a lvalue of type int just fine.

Second, in

template <typename T> void each(std::function<void(T, id)>);
template <typename T> void each(std::function<void(T&, id)>);

The second is more specialized than the first by the partial ordering rules for function templates, so it's always chosen by overload resolution.


A possible solution is to extract the signature by manipulating the type of the lambda's operator ():

template<class T>
struct mem_fn_type;
template<class R, class C, class... T>
struct mem_fn_type<R(C::*)(T...)> {
    using type = std::function<R(T...)>;
};
template<class R, class C, class... T>
struct mem_fn_type<R(C::*)(T...) const> {
    using type = std::function<R(T...)>;
};

// optional extra cv-qualifier and ref-qualifier combos omitted
// since they will never be used with lambdas    

// Detects if a class is a specialization of std::function
template<class T>
struct is_std_function_specialization : std::false_type {};

template<class T>
struct is_std_function_specialization<std::function<T>> : std::true_type{};

// Constrained to not accept cases where T is a specialization of std::function,
// to prevent infinite recursion when a lambda with the wrong signature is passed
template<class T>
typename std::enable_if<!is_std_function_specialization<T>::value>::type each(T func) {
    typename mem_fn_type<decltype(&T::operator())>::type f = func;
    each(f);
}

This won't work for generic lambdas (whose operator() is a template) or for arbitrary function objects (which may have arbitrarily many operator() overloads).



回答2:

Morever, at the time I have to explicitly mention the template type. Is there a way around this?

The canonical solution for this is to implement a generic dependency injection point (i.e. single overload) and allow client code to decide what it puts in there. I am unsure how to give an example that makes sense for the code you provided, because I cannot imagine what a function called each would do with a parameter called iterator (when that parameter is a functor that returns void).

A function similar to yours would be applied for the visitor pattern, so I will give you an example using that:

class collection {
    std::vector<int> data; // to be visited
public:
    void visit(std::function<void(int)> visitor) // single overload
    {
        std::for_each(std::begin(data), std::end(data), visitor);
    }
};

void complex_visitor(int element, double EXTRA, char* PARAMETERS, bool HERE);

Client code:

collection c;
char* data = get_some_data();
bool a = false;
c.visit( [&](int x) { complex_visitor(x, .81, data, a); } );

In this example, it is on the last line (i.e. in client code) that you decide how to plug in a non-matching visitor, not in the interface of the collection class.