views:

171

answers:

1
        template<typename T> T* Push(T* ptr);
        template<typename T> T* Push(T& ref);
        template<typename T, typename T1> T* Push(T1&& ref);

I have

        int i = 0;
        Push<int>(i);

But the compiler calls it ambiguous. How is that ambiguous? The second function is clearly the preferred match since it's more specialized. Especially since the T1&& won't bind to an lvalue unless I explicitly forward/move it.

Sorry - i is an int. Otherwise, the question would make no sense, and I thought people would infer it since it's normally the loop iterator.

+7  A: 

If i is an int, then the first isn't viable. Last two remain. Then, for deduction of i, the second and the third both yield the same function types for overload resolution (both int& as parameter). So you have to rely on partial ordering.

However, partial ordering can't tell them apart. For a function call partial ordering context, only the parameters are used to determine an order (and the return type in your example is not considered), and any reference modifier is peeled off from them. So you will succeed deducing the parameter type from one against the other in both direction - both parameter types will be at least as specialized as the other parameters respectively. And neither has const applied, so neither is more specialized than the other.

There is an issue report placeholder that aims at clarifying anything related to rvalue/lvalue reference difficulties during partial ordering. See this usenet question for details.

If any of the two should be more specialized, i would say that it should the first one. After all, it accepts less arguments than the other one (the other one being a potential perfect forwarder).


Especially since the T1&& won't bind to an lvalue unless I explicitly forward/move it.

Actually, it will accept anything. Having a parameter of type T&& in a template will switch to the "perfect-forwarding-deduction-mode", which will deduce T to the type of the argument if it's an rvalue, and add a lvalue-reference modifier to the type of T if it's an lvalue. So if the argument is an lvalue, the resulting parameter type is T& && collapsed to T&, which accepts lvalues fine (just like in your case).

On a second look, what you seem to be trying to do is to overload a function for taking objects by moving them. But this won't work because of the special deduction done for T&& (see below). Just erase the first function and write your code as

template<typename T, typename T1> T* Push(T1&& ref) {
  /* for lvalues, T1 is U& and rvalues it is U, with U being the
   * argument type. */
  T t1(std::forward<T1>(ref));

  /* whatever needs to be done ... */
}

This will move-construct t1 if the argument was an rvalue, and copy ref if the argument was an lvalue or if T doesn't have a move constructor. This is just an illustration, it may not be what you actually should do depending on your real use-case. I'm also not sure why you have two template parameter types here. I propose to get rid of the T, and say typename remove_reference<T1>::type * for the return type, instead. So that you can gain from argument deduction.

Johannes Schaub - litb