Overload selection with parameter pack and universal references

Viewed 106

I've been trying to use perfect forwarding with a parameter pack that I recursively unpack, and that made me realize I don't really understand the rules that govern how overloads for a given function are selected in the presence of universal references.

My confusion was motivated by some code similar to the following:

#include <iostream>
template<class T>
void write(const T& data)
{
  std::cout << "Called write(const T& data)" << std::endl;
}

template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  std::cout << "Called write(T&& obj, U&&... objs)" << std::endl;
}

int main(int, char**)
{
  int j = 0;
  write(j);
  return 0;
}

When run, the void write(T&& obj, U&&... objs) overload is selected, but if I change the signature of void write(const T& data) to void write(const T data), void write(T& data), or void write(T data) then that function is called.

Why is the void write(const T& data) overload not selected but void write(const T data), void write(T& data), or void write(T data) are?

Edit: I originally though that the issue may have been related to use of std::forward; however it appears to be more a result of the universal references. My original example is below:

#include <iostream>

void write()
{
  std::cout << "Writing nothing" << std::endl;
}

void write(const char* data)
{
  std::cout << "Writing const char*: " << data << std::endl;
}

template<class T>
void write(const T& data)
{
  std::cout << "Writing generic: " << data << std::endl;
}

template<class T, class ...U>
void write(T&& obj, U&&... objs)
{
  write(std::forward<T>(obj));
  write(std::forward<U>(objs)... );
}

int main(int, char**)
{
  int j = 0;
  write("a", j);
  return 0;
}
2 Answers

The answer to this question might be a bit complicated. I'll try to be simpler.

It appears that, after reference collpasing, the candidates for overload resolutions are write(const T&) and write(T&), both with T = int and the second one has U... = (none). This way, the latter one is picked and the forwarding funtion is called again.

In addition to iBug's anwser, it is a special rule that T& beats const T& for lvalue argument of type T. For other cases (const T, T&, T) both implicit conversion sequences are exact matches (T -> const T is qualification adjustment, which is also an exact match) thus neither beats the other, and the first write is more specialized than the second one, so the first one is chosen.

Related