Search code examples
c++templatesc++14variadic-templatesambiguity

Ambiguity while calling template function


I have the following problem:

template< typename T >
class A
{};

class B
{
  public:
    template< typename... T >
    void operator()( A<T>... a )
    {
      std::cout << "A<T>\n";
    }

    template< typename callable >
    void operator()( callable f )
    {
      std::cout << "callable\n";
    }
};


int main()
{
  B      b;
  A<int> a;

  b( a );
}

The call b( a ) is ambiguous -- I expected the output A<T>, i.e., the execution of the first definition of operator(). Does anyone know how to fix it?


Solution

  • Up to and including standard working draft N4606 (this includes the published C++11 and C++14 standards), the call was indeed ambiguous, and Clang was correct to reject it. A change introduced in the latest draft, N4618, makes the partial ordering rules select the A<T>... overload. This is a very recent change; we need to give compilers some time to implement it.

    MSVC 2015 U3 and EDG 4.11 choose the A<T>... overload, so they were non-conforming before and have magically become compliant with the latest draft in this respect.

    What happens is that after template argument deduction we have two overloads that are both template specializations and are equally good based on conversions (identity conversions for both, obviously), so overload resolution has to resort to partial ordering of function templates.

    The process is described in the standard in [temp.deduct.partial], and we're interested in paragraph 8. Before draft N4618, it said:

    If A was transformed from a function parameter pack and P is not a parameter pack, type deduction fails. Otherwise, using the resulting types P and A, the deduction is then done as described in 14.8.2.5. If P is a function parameter pack, the type A of each remaining parameter type of the argument template is compared with the type P of the declarator-id of the function parameter pack. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.

    (emphasis mine above and below)

    Attempting deduction from the first overload to the second, A is a pack and P is not, so the first sentence in the paragraph above applies; deduction fails. Attempting deduction the other way around, the third and fourth sentences apply, but deduction fails again because we're trying to deduce arguments for a parameter of the form A<T> from an argument of a general form callable.

    So, deduction fails both ways; neither template is more specialized than the other; the call is ambiguous.

    The new wording for the paragraph is the following:

    Using the resulting types P and A, the deduction is then done as described in 14.8.2.5. If P is a function parameter pack, the type A of each remaining parameter type of the argument template is compared with the type P of the declarator-id of the function parameter pack. Each comparison deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack. Similarly, if A was transformed from a function parameter pack, it is compared with each remaining parameter type of the parameter template. If deduction succeeds for a given type, the type from the argument template is considered to be at least as specialized as the type from the parameter template.

    Note that the first sentence is gone and was replaced with the one emphasised, which allows deduction from a pack A to a non-pack P.

    Now, deduction succeeds from A<T> to callable thanks to the new rule, and still fails the other way around (nothing changed). This makes the first overload more specialized.


    Quick fix: You can add a leading non-pack parameter to the first overload:

    template<class T, class... Ts> void operator()(A<T>, A<Ts>...)
    

    This will avoid the comparison between a pack and a non-pack for the first position in the function parameter list. A non-pack A<T> is clearly more specialized than callable for all compilers.

    If you need an overload that matches a call with no arguments, provide one separately (sorry...).