2014-10-10 70 views
3

下面的函数重载是不明确的。我发现std::function可以constructed from most callable types,即使他们的签名不匹配。所以编译器无法知道要使用哪个函数。如何使这些std :: function参数明确?传递的λ时

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); 

这里有一些类似的问题,但没有一个能解决我的问题。如何在不改变用途的情况下解决歧义问题?此外,当时我不得不明确提到模板类型。有没有解决的办法?

+3

它不会匹配lambda表达式,编译器无法推导出'T'。 – Jamboree 2014-10-10 12:17:24

+0

@Jamboree当我删除前三个重载,它编译罚款'manager.entity.each ([=](type :: window&window,id entity){/ * ... * /} );' – danijar 2014-10-10 12:20:22

+0

哦,好的,所以你明确指定'T'。在C++ 14中,'std :: function'是sfinae友好的,所以它不会被那些不可调用的构造。 – Jamboree 2014-10-10 12:25:00

回答

6

这其中的一半是LWG issue 2132,从重载解析中移除std::function的构造函数,除非参数实际上可以为指定的参数类型调用。这需要表达SFINAE支持才能实现,哪些VC++没有。

问题的另一半是重载解析:

#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){}); 
} 

用实现LWG2132,this code prints,也许令人惊讶的库:

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

为什么?首先,可以从[](int, id){}构建std::function<void(T&, id)>。毕竟,后者可以被称为与int类型的左值就好了。

其次,在

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

第二个比第一个由函数模板部分排序规则更加专业化的,所以它总是通过重载决议选择。


一个可能的解决方案是通过操纵类型提取签名拉姆达的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); 
} 

这不会对通用Lambda表达式工作(其operator()是一个模板)或任意函数对象(可能会有任意多个过载)。

+0

是不是有一种方法可以支持前半部分的问题,通过使用像'template void each(T iterator);'并且在内部转换为'std :: function'的签名? – danijar 2014-10-10 13:37:25

+0

@danijar请参阅编辑。 – 2014-10-10 14:34:57

+0

谢谢。您能否添加一个使用示例请问我在问题中提供的签名?目前,我的理解不足以应用您的解决方案。 – danijar 2014-10-10 21:33:09

1

Morever,当时我已经明确提到的模板类型。有没有解决的办法?

规范的解决方案是实现一个通用的依赖注入点(即单重载),并允许客户端代码决定它放在那里。我不确定如何给出一个对你提供的代码有意义的例子,因为我无法想象一个称为each的函数会使用一个名为iterator的参数(当该参数是一个返回void的函子时)。

与你相似的函数将被应用于访问者模式,所以我会使用,给你一个例子:

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); 

客户端代码:

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

在这个例子中,它是在最后一行(即在客户端代码中),你决定如何插入一个不匹配的访问者,而不是在collection类的接口。

+0

是的,我使用访客模式。也许我不应该从这个例子中删除太多。但是,我有四个(没有区别价值和参考两个)模板化的重载。那就是问题所在。 – danijar 2014-10-10 13:34:44

+0

如果四个重载实现相同的算法,我认为你应该有一个单一的实现;如果他们实现不同的算法,他们会做不同的事情,并且可能应该以不同的方式命名(从而完全避免冲突)。我无法想象这样一种情况,即功能应该被称为相同但完全不同的事情(并且在那种情况下,我没有解决方案 - 至少在六个月内没有变成一个难以理解的维护噩梦: () – utnapistim 2014-10-10 14:44:47

+0

正如一些人所指出的那样,我不能仅仅通过引用而不是一个值来重载,所以我留下了两个重载,为访问者提供了额外的'id'参数,我可以有一个单一的实现同时支持,一个参数为'T'的lambda表达式和两个参数'T,id'的lambdas? – danijar 2014-10-10 15:26:05

相关问题