Search code examples
c++templateslanguage-lawyeroverload-resolution

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


I have common code – Dijkstra's algorithm – I use in different contexts, so I decided to use tag dispatch.

The common code is in the following function (you can see End get dispatched depending on the Tag template parameter):

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

For most of the contexts I define a default no-op as follows:

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

For one context I define the function with the following signature:

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

Everything worked as expected, till I found I forgot & in the signature after Distances – I was copying Distances, a large unordered_map, every time.

However, after I changed it to const Distances& to avoid expensive copying, the less specialized noop version got called. I have no idea why. And how to fix it.

(I swear the change is only in adding a single character &. Or const&)

(The signature is otherwise correct, if I comment out the generic noop version, it just uses the OneContextTag version.)

(The code is more complex, but I hope it can be figured out from this.)


Solution

  • I do not have an answer to why overload resolution works the way it does here atm. But I have a potential solution for you, which is also (IMO) more robust:

    Change the default End to accept a UseDefaultEnd tag as the first parameter. For each context which should use the default End, subclass its tag from 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>();
    }