两个函数模板候选项.将一个参数作为引用后,选择不太专业的模板



我有共同的代码 - Dijkstra的算法 - 我在不同的上下文中使用,所以我决定使用标签调度。

通用代码在以下函数中(您可以看到End根据 Tag 模板参数进行调度(:

template <typename Tag, typename ... Args>
void Dijkstra(blahblah, Args&&... arg) {
...
if (End(Tag(), cost, n_id, distances, time_limit, args ...)) {
break;
}

对于大多数上下文,我定义默认的无操作,如下所示:

template<typename ... Args>
bool inline End(Args&& ...) {
return false;
}

对于一个上下文,我使用以下签名定义函数:

bool inline End(OneContextTag, Duration d, NodeId n_id, Distances distances, Du time_limit, blahblah) {

一切都按预期进行,直到我发现我在Distances后忘记了签名中的&——我每次都在复制距离,一个大unordered_map。

但是,在我将其更改为const Distances&以避免昂贵的复制后,调用了不太专业的noop版本。我不知道为什么。以及如何解决它。

(我发誓更改仅在添加单个字符&。或const&(

(签名在其他方面是正确的,如果我注释掉通用的noop版本,它只使用OneContextTag版本。

(代码更复杂,但我希望可以从中弄清楚。

所以你要问的基本上是为什么下面的程序打印Special fooGeneric bar

struct A {};
template<class ... Args>
void foo(Args&&...)
{
std::cout << "Generic foon";
}
void foo(A)
{
std::cout << "Special foon";
}
template<class ... Args>
void bar(Args&&...)
{
std::cout << "Generic barn";
}
void bar(A const&)
{
std::cout << "Special barn";
}
int main()
{
A a;
foo(a);
bar(a);
}

让我们看看重载解析会发生什么:

1. 选择候选函数。

C++11/[over.match.funcs]/7在候选项是函数模板的每种情况下,候选函数模板专用化都是使用模板参数推导(14.8.3、14.8.2(生成的。然后,以通常的方式将这些候选项作为候选函数进行处理。

foo(a)的候选人:

template<> void foo<A&>(A&); // reference collapsing
void foo(A);

bar(a)的候选人:

template<> void bar<A&>(A&); 
void bar(A const&);

2. 选择最佳可行功能:

首先,如果(至少(其中一个参数具有更好的转换序列(并且没有其他参数具有更差的转换序列(,则重载会更好。

C++11/[over.ics.rank]/3标准转换序列S1 是比标准转换序列 S2 更好的转换序列,如果[ ... ]S1 和 S2 是引用绑定 (8.5.3(,引用引用的类型是相同的类型,除了顶级 cv 限定符,并且 S2 初始化的引用引用的类型比S1 初始化的引用引用的类型更符合 cv 资格

这会导致模板候选bar的首选项,因为调用void bar(A const&)所需的转换需要将左值绑定到更符合 cv 条件的 const 右值引用。因此,您会看到使用Distances const&时调用的通用版本。

C++11/[over.best.ics]/6当参数类型不是引用时 [ ... ]
当参数具有类类型且参数表达式具有相同的类型时,隐式转换序列是标识转换。

这使得参数的转换序列在传递给标识转换void foo(A)a(模板函数也是如此(。

如果两个重载都没有更好的转换顺序,则非模板版本优先于模板。

C++11/[over.match.best]/1[ ... ]给定这些定义,可行函数 F1 被定义为比另一个可行函数 F2 更好的函数,如果 [ ... ]F1 是非模板函数,F2 是函数模板专用化。

foo就是这种情况,当您使用Distances distances时,您的代码会按照您的预期运行。

我没有答案为什么过载分辨率在这里 atm 的工作方式。 但我有一个潜在的解决方案给你,它也(IMO(更强大:

更改默认End以接受UseDefaultEnd标记作为第一个参数。 对于每个应该使用默认End的上下文,从UseDefaultEnd子类化其标签:

#include <iostream>
struct UseDefaultEnd {};
/* Comment first parameter; then you get the same behavior as 
you're currently trying to solve. */
template<typename ... Args>
bool inline End(UseDefaultEnd, Args&& ...) {
// bool inline End(Args&& ...) {
return false;
}
struct OneTag {};
struct OtherTag : public UseDefaultEnd {};
bool inline End(OneTag, int const & i) {
return true;
}
template<typename Tag>
void Caller() {
int i = 42;
if (End(Tag(), i)) {
std::cout << "Used specific version of End" << std::endl;
}
}

int main() {
Caller<OtherTag>();
std::cout << "---" << std::endl;
Caller<OneTag>();
}

最新更新