Why is template constructor preferred to copy constructor?
D

3

41
#include <iostream>

struct uct
{
    uct() { std::cerr << "default" << std::endl; }

    uct(const uct &) { std::cerr << "copy" << std::endl; }
    uct(      uct&&) { std::cerr << "move" << std::endl; }

    uct(const int  &) { std::cerr << "int" << std::endl; }
    uct(      int &&) { std::cerr << "int" << std::endl; }

    template <typename T>
    uct(T &&) { std::cerr << "template" << std::endl; }
};

int main()
{
    uct u1    ; // default
    uct u2( 5); // int
    uct u3(u1); // template, why?
}

coliru

Template overload of the constructor fits to both declarations (u2 and u3). But when int is passed to the constructor, a non-template overload is chosen. When the copy constructor is called, a template overload is chosen. As far as I know, a non-template function is always preferred to a template function during overload resolution. Why is the copy constructor handled differently?

Deviate answered 12/9, 2019 at 15:23 Comment(2)
"Too perfect forwarding". akrzemi1.wordpress.com/2013/10/10/too-perfect-forwardingLimoli
There is zero opinion-based content in this question. Why the VTC as POB?Navarre
D
41

As far as I know non-template function is always preferred to template function during overload resolution.

This is true, only when the specialization and the non template are exactly the same. This is not the case here though. When you call uct u3(u1) The overload sets gets

uct(const uct &)
uct(uct &) // from the template

Now, since u1 is not const it would have to apply a const transformation to call the copy constructor. To call the template specialization it needs to do nothing since it is an exact match. That means the template wins as it is the better match.

To stop this one thing you can do is use SFINAE to limit the template function to only be called when T is not a uct. That would look like

template <typename T, std::enable_if_t<!std::is_same_v<uct, std::decay_t<T>>, bool> = true>
uct(T &&) { std::cerr << "template" << std::endl; }
Danish answered 12/9, 2019 at 15:28 Comment(6)
Just to add to it, this is why templated constuctors are weird beasts, and unless you want them to overtake everything, you probably want to tag the constructor. (Similar to inplace_t)Remorseful
@Remorseful Or use SFINAE.Danish
Or that, but tagging is usually easierRemorseful
adding const transforms the forward reference to a rvalue reference. therefore it isn't preferred because of the non-template vs. template rule, but because uct u3(u1) doesn't match at allMammillate
@Mammillate Good catch. I've removed that section.Danish
overloading any function where one takes a forwarding reference is fraught.Loosetongued
Z
5

When copy constructor is tried to be called, template overload is chosen. As far as I know non-template function is always preferred to template function during overload resolution. Why is copy constructor handled differently?

template <typename T>
uct(T &&) { std::cerr << "template" << std::endl; }
//    ^^

The reason the templated version gets picked is because the compiler is able
to generate a constructor with signature (T &) which fits better and therefore is chosen.

  • If you changed the signature from uct u1 to const uct u1 then it would fit the copy constructor (since u1 is not const to begin with).

  • If you changed the signature from uct(const uct &) to uct(uct&) it would be a better fit and it would choose that over the templated version.

  • Also, the uct(uct&&) would be chosen if you had used uct u3(std::move(u1));


To fix this you can use SFINAE to disable the overload when T is the same as uct:

template <typename T, std::enable_if_t<!std::is_same_v<std::decay_t<T>, uct>>>
uct(T&&)
{
  std::cerr << "template" << std::endl;
}
Zinkenite answered 12/9, 2019 at 15:59 Comment(0)
C
3

The problem is that the template constructor has no the qualification const while the non-template copy constructor has the qualifier const in its parameter. If you will declare the object u1 as a const object then the non-template copy constructor will be called.

From the C++ STandard (7 Standard conversions)

1 Standard conversions are implicit conversions with built-in meaning. Clause 7 enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:

(1.4) — Zero or one qualification conversion

So the copy constructor needs one standard conversion while the template constructor sies not require such a conversion.

Carrefour answered 12/9, 2019 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.