两个函数模板候选。在将一个参数作为参考后,选择不太专业的模板

Two function template candidates. After making one argument a reference, the less specialized template gets chosen

我有通用代码——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之后的签名中忘记了& – 我每次都在复制Distances,一个大unordered_map。

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

(我发誓只是添加了一个字符 &。或者 const&

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

(代码比较复杂,但希望能从这里算出来。)

我不知道为什么重载解析会像这里的 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>();
}

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

struct A {};

template<class ... Args>
void foo(Args&&...)
{
    std::cout << "Generic foo\n";
}

void foo(A)
{
    std::cout << "Special foo\n";
}

template<class ... Args>
void bar(Args&&...)
{
    std::cout << "Generic bar\n";
}

void bar(A const&)
{
    std::cout << "Special bar\n";
}

int main()
{
    A a;
    foo(a);
    bar(a);
}

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

1。候选函数被选中。

C++11/[over.match.funcs]/7 In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way.

呼叫foo(a)的候选人:

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

呼叫 bar(a) 的候选人:

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

2。 Select 最佳可行函数:

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

C++11/[over.ics.rank]/3 Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if [ ... ] S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

这导致 bar 的候选模板的偏好,因为调用 void bar(A const&) 所需的转换需要将左值绑定到更多 cv 限定的 const 左值引用。 因此,您看到使用 Distances const& 时调用的通用版本。

C++11/[over.best.ics]/6 When the parameter type is not a reference [ ... ]
When the parameter has a class type and the argument expression has the same type, the implicit conversion sequence is an identity conversion.

这使得参数 a 传递给 void foo(A) 时的转换序列成为身份转换(模板函数也是如此)。

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

C++11/[over.match.best]/1 [ ... ] Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if [ ... ] F1 is a non-template function and F2 is a function template specialization.

这就是 foo 的情况,并使您的代码在您使用 Distances distances.

时按预期运行