Вопрос

Compiling following code in VS2012 without any problem.

struct Foo
{
    typedef int DummyType;
};

template<typename T>
int Bar(T* foo, typename T::DummyType* dummy_ = 0) { return 0; }

template<typename T>
int Bar(T* foo, ...) { return 1; }

template<typename T>
int Bar(typename T::DummyType* dummy_ = 0) { return 2; }

template<typename T>
int Bar(...) { return 3; }


void fn()
{
    Bar((Foo*)NULL);
    Bar((int*)NULL);

    Bar<Foo>();
    Bar<int>();
}

But trying VS2013RC got following errors. Is this a VS2013RC bug or problem with the code itself. What the standard says about match overload function with template function specialization and variadic functions.

1>c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(25): error C2668: 'Bar' : ambiguous call to overloaded function
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(15): could be 'int Bar<Foo>(T *,...)'
1>          with
1>          [
1>              T=Foo
1>          ]
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(12): or       'int Bar<Foo>(T *,Foo::DummyType *)'
1>          with
1>          [
1>              T=Foo
1>          ]
1>          while trying to match the argument list '(Foo *)'
1>c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(28): error C2668: 'Bar' : ambiguous call to overloaded function
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(21): could be 'int Bar<Foo>(...)'
1>          c:\users\dummy\documents\visual studio 2013\projects\test\test.cpp(18): or       'int Bar<Foo>(Foo::DummyType *)'
1>          while trying to match the argument list '()'

Thanks for any help!


Thanks for the answer!

I just made a new test as follow:

struct Foo
{
    typedef int DummyType;
};

// Bar0 #0
template<typename T>
static int Bar0(const T* foo, typename T::DummyType* dummy_) { return 0; }

// Bar0 #1
template<typename T>
static int Bar0(const T* foo, ...) { return 1; }


template<typename T, typename U>
struct DummyType2 {};

// Bar1 #2
template<typename T>
static int Bar1(const T* foo, DummyType2<T, typename T::DummyType>* dummy_) { return 2; }

// Bar1 #3
template<typename T>
static int Bar1(const T* foo, ...) { return 3; }

void fn()
{

    std::cout<<Bar0((Foo*)NULL, NULL)<<std::endl;   // call 0 matches Bar0 #0

    std::cout<<Bar1((Foo*)NULL, NULL)<<std::endl;   // call 1 matches Bar1 #3
}

the output is

0
3

What is the reason call 0 matches Bar0 #0, but call 1 matches Bar1 #3. Any rules from standard?

Это было полезно?

Решение

Numbering those four overloads for reference:

template<typename T>
int Bar(T*, typename T::DummyType* = 0);             // #1

template<typename T>
int Bar(T*, ...);                                    // #2

template<typename T>
int Bar(typename T::DummyType* = 0);                 // #3

template<typename T>
int Bar(...);                                        // #4

As per [temp.deduct.type]/5, typename T::DummyType is a non-deduced context for T. I.e., the parameter typename T::DummyType* dummy_ cannot be used to deduce T. Therefore, for the first two calls

Bar((Foo*)NULL);
Bar((int*)NULL);

T can be deduced for the first two overloads, but not for the second two. This is why overloads #3 and #4 are not viable for these calls. After this deduction, every occurrence of T in the function signature will be substituted with the deduced type. This can result in a substitution failure, see call 2 below.


For the first call, the following two overloads are viable:

/*substituted template*/
int Bar<Foo>(Foo*, Foo::DummyType* = 0);             // #1

/*substituted template*/
int Bar<Foo>(Foo*, ...);                             // #2

As per [over.match.viable]/2, the default arguments are ignored for overload resolution:

First, to be a viable function, a candidate function shall have enough parameters to agree in number with the arguments in the list.

  • If there are m arguments in the list, all candidate functions having exactly m parameters are viable.
  • A candidate function having fewer than m parameters is viable only if it has an ellipsis in its parameter list (8.3.5). For the purposes of overload resolution, any argument for which there is no corresponding parameter is considered to “match the ellipsis” (13.3.3.1.3).
  • A candidate function having more than m parameters is viable only if the (m+1)-st parameter has a default argument (8.3.6). For the purposes of overload resolution, the parameter list is truncated on the right, so that there are exactly m parameters.

So, we actually compare those two signatures here:

/*substituted template*/
int Bar<Foo>(Foo*);                                 // #1

/*substituted template*/
int Bar<Foo>(Foo*);                                 // #2

These two rank as Exact Matches and therefore are ambiguous.


For the second call, we have a substitution failure for the first overload (see below), so it's not in the list of viable functions. Only one overload remains viable:

/*substituted template*/
int Bar<int>(int*);                                 // #2

Substitution failure:

For the second call Bar((int*)NULL);, the substitution of T for int results in a substitution failure in the first overload [temp.deduct]/5:

When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in the template parameter list of the template and the function type are replaced with the corresponding deduced or default argument values. If the substitution results in an invalid type, as described above, type deduction fails.

The invalid type here is int::DummyType.


For the third and fourth call, only the last two overloads are viable (because of the number of arguments). The rest is similar to the first two overloads.

The third call has to select from the overloads

/*substituted template*/
int Bar<Foo>(Foo::DummyType* = 0);                   // #3

/*substituted template*/
int Bar<Foo>(...);                                   // #4

which is ambiguous like the first call.


For the fourth call, the third overload results in a substitution failure, and only the fourth overload remains viable (and is selected unambiguously).


Follow-up question.

First call: Bar0((Foo*)NULL, NULL)

Overloads:

// Bar0 #0
template<typename T>
static int Bar0(const T* foo, typename T::DummyType* dummy_);

// Bar0 #1
template<typename T>
static int Bar0(const T* foo, ...);

In Bar0 #0, T is again in an non-deduced context, therefore only the first argument is used for deduction. The substituted template signatures look like:

// substituted template
static int Bar0<Foo>(const Foo*, Foo::DummyType*);  // #0

// substituted template
static int Bar0<Foo>(const Foo* foo, ...);          // #1

The definition of NULL now becomes somewhat relevant:

[support.types]/3

The macro NULL is an implementation-defined C++ null pointer constant in this International Standard.

[conv.ptr]/1

A null pointer constant is an integral constant expression (5.19) prvalue of integer type that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type.

The exact type of NULL isn't specified (one more reason to use nullptr instead!). But we know it's convertable to Foo::DummyType*. This conversion is a standard conversion. Matching NULL with the ellipsis is a so-called ellipsis conversion; it's not really a conversion, only in terms of overload resolution [over.ics.ellipsis]/1:

An ellipsis conversion sequence occurs when an argument in a function call is matched with the ellipsis parameter specification of the function called (see 5.2.2).

Now, the two overloads are viable and have to be ranked. Fortunately, this is simple here [over.ics.rank]/2

a standard conversion sequence is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence

Therefore, the conversion sequence type of NULL to Foo::DummyType*, as required for the overload #0, is the better conversion sequence, when compared to the ellipsis conversion sequence for matching NULL with the ... of overload #2.

[over.match.best] now specifies that the function selected is the one with the best conversion sequence. Therefore, overload #0 is unambiguously selected.


Second call: Bar1((Foo*)NULL, NULL)

Overloads:

// Bar1 #2
template<typename T>
static int Bar1(const T*, DummyType2<T, typename T::DummyType>*);

// Bar1 #3
template<typename T>
static int Bar1(const T*, ...);

Here, the important part is the T in DummyType2<T, ..>. It is not in a non-deduced context. Therefore, the compiler tries deduce T from the first and the second argument. As the second argument in the call has some unspecified integral or std::nullptr_t type, type deduction fails for overload Bar1 #2. Overload Bar1 #3 remains viable and is unambiguously selected.

If you however change overload Bar1 #2 to:

// Bar1 #2
template<typename T>
static int Bar1(const T*, DummyType2<int, typename T::DummyType>*);

Then T is deduced only from the first argument, and this overload is preferred & selected (for the same reason as in the first call of the follow-up question).

You could also (instead of changing the overload) change the second call to:

Bar1((Foo*)NULL, (DummyType2<Foo, int>*)NULL)

This way, T can be unambiguously deduced to Foo, and the overload Bar1 #2 is selected.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top