简体   繁体   中英

c++ templates and ambiguous call to overloaded function

I'm stuck with the following problem. I was using Xcode 7 and had no issue with my project. After trying to compile it on Visual Studio Express 2015, I get the message "Error C2668 ambiguous call to overloaded function" in the code. I couldn't find anything specific to visual studio related to the problem.

I made a minimal working example supposed to be used on VS (there's no error on Xcode). The weird part involves the func2 part. It's as if VS compiler couldn't auto deduce more types and/or arguments than a limit.

Update :

A workaround was proposed by Sam Varshavchik consisting in using partial specialization with an intermediate template class. This is a solution that I would like to avoid. First because it's not convenient in the context it would apply in my code, second because this compilation error is unclear to me. This error doesn't show up in Xcode7, and func2 has no error even in VS. Despite the explanation of WhiZTiM to which I agree with, fact is, overloading in this context can sometimes work, and sometimes not. And I'd really like to know why.

Update 2 :

According to bogdan, this is probably a bug in GCC and MSVC. I'm going to try to report it. (I love so much this first week with visual studio)

Update 3 :

Bug reported at https://connect.microsoft.com/VisualStudio/feedback/details/3076577

functions.h :

template <class T>
class BX {
public :
    BX() {}
    ~BX() {}
};

template <class T1, class T2>
class G {
public :
    G() {}
    ~G() {}
};

template <template <class T> class T1, class T>
class DT {};

class B {
public :
    //I want func to work
    template <template <class T> class T1, class T, class M>
    static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}

    template <template <class T> class T1, class T, class M>
    static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}

    //here is a small variation of func as a test
    template <template <class T> class T1, class T, class M>
    static void func2(const M& m, const DT<T1, T>* dt) {}

    template <template <class T> class T1, class T, class M>
    static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};

main.cpp

int main() {
    BX< int > bx;
    G<BX< int >, int> g;
    DT<BX, int>* dt;
    B::func(g, dt, bx);//Error  C2668   'B::func': ambiguous call to overloaded function
    B::func2(g, dt);//no error
}

You made this call:

B::func(g, dt, bx);

Where:

  • g is of type G<BX< int >, int>
  • dt is of type DT<BX, int>*
  • bx is of type BX< int >

Now you have these two functions:

template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
               //^^^^^^^^^^

template <template <class T> class T1, class T, class M>
static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
               //^^^^^^^^^^^^^^^^^^

During overload resolution; and considering only the first parameters in your function declarations (since those are what supposedly makes the function declarations different):

  • M in the first function overload gets deduced as G<BX< int >, int> .
  • The second function overload has a templated type that should be matched.
    • T can be deduced from bx as an int
    • T1<T> a template template type and is deduced from bx as BX< int >
    • M will match anything.
    • At the end of the day, you have that first parameter deduced as G<BX< int >, int> which is same as that of the first function

GCC also raises an ambiguity error.


To give preference to the second overloaded function whenever a type of G<...> is passed, you'll need to use partial specializations. (Since they are ranked higher than primary templates). Please see Sam Varshavchik's answer for one possible way of doing this.

The apparent intent of the shown code is a partial function specialization.

Which ...is not going to work.

So, what to do, what to do... Well, how about transmogrifying a partial function specialization into an ordinary template class specialization?

My solution specializes a template on the first function parameter type in order to disambiguate the static class, and forward it to one of two ultimate class methods.

A good C++ compiler should be able to optimize away the extra function call layer:

template <class T>
class BX {
public :
    BX() {}
    ~BX() {}
};

template <class Tdata, class Tmetric>
class G {
public :
    G() {}
    ~G() {}
};

template <template <class T> class T1, class T>
class DT {};

template<class M> class B_func;

class B {
public :
    template <template <class T> class T1, class T, class M>
    static void func(const M& m, const DT<T1, T>* dt, T1<T>& val)
    {
        B_func<M>::func(m, dt, val);
    }

    template <template <class T> class T1, class T, class M>
    static void func_a(const M& m, const DT<T1, T>* dt, T1<T>& val) {}

    template <template <class T> class T1, class T, class M>
    static void func_b(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}

    //here is a small variation of func as a test
    template <template <class T> class T1, class T, class M>
    static void func2(const M& m, const DT<T1, T>* dt) {}

    template <template <class T> class T1, class T, class M>
    static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};


template <class M>
class B_func {
public:
    template<class two, class three>
    static void func(const M& m, const two* dt, three& val)
    {
        B::func_a(m, dt, val);
    }
};

template <template <class T> class T1, class T, class M>
class B_func<G<T1<T>, M>> {
public:
    template<class two, class three>
    static void func(const G<T1<T>, M>& m, const two* dt, three& val)
    {
        B::func_b(m, dt, val);
    }
};


int main() {
    BX< int > bx;
    G<BX< int >, int> g;
    DT<BX, int>* dt;
    B::func(g, dt, bx);
    B::func2(g, dt);

    return 0;
}

This looks like a bug in MSVC and GCC. The call should resolve to the second overload (Clang and EDG are doing that).

For the call B::func(g, dt, bx) , name lookup finds the two func templates. Template argument deduction and substitution is performed on each of them in order to generate function template specialization declarations that can subsequently participate in overload resolution. Deduction succeeds for both templates and we're left with two specializations:

void B::func<BX, int, G<BX<int>, int>>(const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);
void B::func<BX, int, int>            (const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);

The two functions have identical parameter declaration clauses, so clearly overload resolution cannot distinguish between them based on conversions from the arguments of the call; it has to resort to the last two steps in the process.

First, if one of the functions is a template specialization and the other one is not, the non-template one is preferred; not applicable here.

Last, it looks at the templates from which the two specialization declarations were synthesized; if one of the templates is more specialized than the other according to the partial ordering of function templates, then the corresponding specialization is preferred. (This is the only place in the process where the original templates come back into play.)

The description below is not very accurate and skips quite a few details, but I'm trying to concentrate on the parts that are relevant to this case. Very roughly:

  • First, references and cv-qualifiers are stripped from the function parameter declarations of both templates, yielding:

     F1(M , const DT<T1, T>*, T1<T>) F2(G<T2<U>, V>, const DT<T2, U>*, T2<U>) 

    (template parameter names changed to avoid confusion)

  • Then, deduction is attempted as if for a call to one template using the forms of the function parameters of the other template as arguments, and then the other way around. In this case, the last two pairs of corresponding parameters have identical forms, so deduction succeeds both ways. For the first pair of corresponding parameters:

    • Deducing M from an argument of the form G<T2<U>, V> works; M is deduced as G<T2<U>, V> .
    • Deducing T2 , U and V in G<T2<U>, V> from an argument of the form M doesn't work ( M can be anything).

    In other words, G<T2<U>, V> is a "more specific" form than M ; it cannot represent all the types that M can represent; this is the intuitive meaning that more specialized is trying to formalize in this context.

  • So, deduction works for all pairs of corresponding parameters from F2 to F1 , but not the other way around. This makes F2 more specialized than F1 in terms of partial ordering.

This means that the specialization corresponding to the second template is preferred in overload resolution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM