Why is template constructor preferred to copy constructor? - c++

#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?

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; }

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;
}

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.

Related

Conversion constructor strangely not competing with copy constructor

Implementing a copy constructor deletes the default move constructor in C++.
Only compiler generated copy and move constructors are trivial.
Created a templated conversion constructor from any type to the current type.
#include <format>
#include <iostream>
#include <type_traits>
template <typename T = int>
class Element {
public:
T value;
Element(const T value_) noexcept : value(value_) {};
// Here is the conversion constructor
template <typename TT>
Element(const Element<TT> &element) noexcept : value(element.value) {
std::cout << std::format(
"Element<{}>(const {}& {})\n",
typeid(T).name(), typeid(TT).name(), element.value
);
}
// uncommenting this breaks the assertions coming next
// Element(const Element &element) noexcept : value(element.value) {};
};
static_assert(std::is_trivially_move_constructible<Element<>>::value);
static_assert(std::is_trivially_copy_constructible<Element<>>::value);
// how it behaves
void foo_int(Element<int> element) {
std::cout << std::format("foo_int: {}\n", element.value);
}
void foo_double(Element<double> element) {
std::cout << std::format("foo_double: {}\n", element.value);
}
int main() {
Element<int> int_element {1};
Element<double> double_element {1.5};
foo_int(int_element);
foo_double(int_element);
// uncommenting doesn't compile - narrowing conversion
// foo_int(double_element);
foo_double(double_element);
return 0;
}
Demo in Compiler Explorer
Can someone explain to me why this conversion constructor is not matched with T == TT. In that case only the copy / move constructor is called.
Then I thought maybe I can call it manually with auto a = Element<int>::Element<int>(int_element);. But that gives an error: "obsolete declaration style".
It seems to be treated like a normal constructor, and only considered after the other special member functions.
Can someone explain me the rules or where I can read more about this behavior?
When type T is the same as TT, then you are making a copy. The standard explicitly states that the copy constructor is not a template:
non-template constructor for class X is a copy constructor if its
first parameter is of type X&, const X&, volatile X& or const volatile
X&, and either there are no other parameters or else all other
parameters have default arguments ([dcl.fct.default]).
https://eel.is/c++draft/class.copy.ctor#1
Also, in C++, a non-template is always preferred over a template if it's an exact match. Given there is a real copy constructor, and your templated conversion constructor, in copying contexts the copy constructor would be picked. The same thing would happen for ordinary functions too:
template <typename T>
void foo(T);
void foo(int);
foo(123); // will call the non-template version (since it's an exact match)

C++ class templates can be implicity specialized and instantiated without angle brackets?

This actually compiles and works, but it's unclear to me why.
#include <iostream>
template <class T>
class LikeA
{
T m_val{};
public:
LikeA() = default;
explicit LikeA(T iv): m_val(std::move(iv)) {}
LikeA(LikeA<T> const &) = default;
LikeA(LikeA<T> &&) noexcept = default;
~LikeA() noexcept = default;
operator T const &() const { return m_val; }
LikeA<T> &operator=(T nv) { m_val = std::move(nv); return *this; }
LikeA<T> &operator=(LikeA<T> const &n) { m_val = n.m_val; return *this; }
LikeA<T> &operator=(LikeA<T> &&n) { m_val = std::move(n.m_val); return *this; }
};
template <class T>
T f (LikeA<T> i)
{
return i;
}
int main()
{
std::cout << f(LikeA{3.1415927}) << '\n'; // No template argument? Not a syntax error?
return 0;
}
I was previously calling f like f(3.1415927) before I let a lint checker talk me into making one of LikeAs constructors explicit. After that, of course, it couldn't implicitly convert the constant to a LikeA. If you just add braces (i.e. f({3.1415927}) the compiler still doesn't know what to select.
In my full code the actual template argument is a lot more verbose, so just for grins I put the template name LikeA in front of the brace initializers, fully expecting a syntax error.
To my surprise, it compiled and ran.
Since this was MSVC, at first I though it was just Microsoft lulling me into a sense of false security. But I tested it against several compilers (gcc, clang, zigcc) in Compiler Explorer, and it works on all of them.
How does C++ select the correct template specialization? On the surface, argument-dependent lookup would seem to be the answer, but notice there are no angle brackets, and the template doesn't have a default argument. I definitely remember this being a syntax error at some point in the past.
(Function template specialization without templated argument doesn't answer this because OP actually specifies the arguments).
The cppreference on function template arguments has a quick aside about omitting <> but this is a class template. The syntax here appears to require the angle brackets all the time.
Since C++17, compiler can automatically deduce the argument type of a template by using class template argument deduction (CTAD). You can skip defining the templates arguments explicitly if the constructor is able to deduce all template parameters.
So you simply write
int main()
{
std::vector v{2, 4, 6, 8}; // same as std::vector<int>
std::list l{1., 3., 5.}; // same as std::list<double>
std::pair p{false, "hello"}; // same as std::pair<bool, const char *>
std::cout << typeid(v).name() << std::endl;
std::cout << typeid(l).name() << std::endl;
std::cout << typeid(p).name() << std::endl;
}
Under MSVC, it produces the following output
class std::vector<int,class std::allocator<int> >
class std::list<double,class std::allocator<double> >
struct std::pair<bool,char const * __ptr64>
Kindly refer CTAD for more details.

Generic constructor template called instead of copy/move constructor

I've designed a simpler wrapper class that adds a label to an object, with the intent of being implicitly convertible/able to replace the wrapped object.
#include <string>
#include <type_traits>
#include <utility>
template < typename T, typename Key = std::string >
class myTag{
T val;
public:
Key key;
template < typename... Args,
typename = typename std::enable_if< std::is_constructible< T, Args... >::value >::type >
myTag(Args&&... args) :
val(std::forward< Args >(args)...) {
std::cout << "forward ctor" << std::endl;
}
myTag(const myTag& other) :
key(other.key), val(other.val) {
std::cout << "copy ctor" << std::endl;
}
myTag(myTag&& other):
key(other.key), val(other.val) {
std::cout << "move ctor" << std::endl;
}
operator T&() { return val; }
operator const T&() const { return val; }
};
int main(int argc, char const *argv[]) {
myTag< float > foo(5.6); // forward ctor
myTag< float > bar(foo); // forward ctor
return 0;
}
However, I am having trouble properly declaring & defining the copy/move constructor. I declared a generic constructor overload template that forwards its arguments to the underlying type, as long as such construction is possible. However, due to the implicit conversion operators, it is capturing every instantiation of myTag, effectively shadowing the copy/move constructors.
The point of non-default copy/move semantics was to copy/move the key value vs default-initializing it with constructor template.
How do I make the compiler prefer/give precedence to the explicit copy/move constructors vs the generic overload? Is there any additional SFINAE check alternative to is_constructible<> that avoids implicit conversions?
EDIT: I should add that I'm looking for a C++14 solution.
It isn't shadowing the copy and move constructors. It is just beating them in overload resolution in some cases.
If you pass a myTag<float>&, the forwarding constructor is used.
If you pass a const myTag<float>& the copy constructor is used.
If you pass a myTag<float>&&, the move constructor is used.
If you pass a const myTag<float>&&, the forwarding constructor is used.
The copy and move constructors are not templates, so they will win against a template of the same signature. But the forwarding constructor can be a better match in the cases where the deduced signature differs from the copy and move constructors.
The idiomatic way to handle this is to remove the forwarding constructor from consideration based on the decayed type of the arguments:
template <typename... Args>
constexpr myTag(Args&&... args)
requires
((sizeof...(Args) != 1 && std::is_constructible_v<T, Args...>) ||
(sizeof...(Args) == 1 && std::is_convertible_v<Args..., T> && !std::is_same_v<myTag, std::decay_t<Args>...>))

non-template std::reference_wrapper assignment operator and template constructor

In the C++ 20 Standard the constructor of the class template std::reference_wrapper is a template.
template<class U>
constexpr reference_wrapper(U&&) noexcept(see below );
while the assignment operator is not a template
constexpr reference_wrapper& operator=(const reference_wrapper& x) noexcept;
What is the reason that this difference (template and non-template) between these special member functions exists?
On the other hand, I tried the following progarm using Visual C++ 2019.
#include <iostream>
#include <functional>
struct A
{
void f() const { std::cout << "A::f()\n"; }
virtual void g() const { std::cout << "A::g()\n"; }
};
struct B : A
{
void f() const { std::cout << "B::f()\n"; }
void g() const override { std::cout << "B::g()\n"; }
};
int main()
{
B b;
std::reference_wrapper<A> r( b );
r.get().f();
r.get().g();
r = std::reference_wrapper<B>( b );
}
and the compiler did not issue an error message relative to the assignment operator.
Is it a bug of the compiler or have I missed something?
If std::reference_wrapper<T> had a single constructor accepting T&, then it would lead to bugs like std::reference_wrapper<const T> being able to bind to temporaries of type T. So, originally there were two constructors (other than the copy constructor): one taking T&, and another taking T&&, which was defined as deleted, ensuring that a compile error would occur.
However, it was pointed out that this is not really what we want: it would be better if std::reference_wrapper<T> would have no constructor at all that accepts an rvalue of type T, as opposed to a deleted constructor. This is LWG 2993. (You'll notice that this issue is mentioned at the bottom of the cppreference page.) Thus, the constructor was changed to a template that is SFINAE-disabled as appropriate.
Once these issues are solved for the constructor, the assignment operator only needs to have a single overload taking reference_wrapper. The conversion issues will be handled by the constructor logic when the compiler forms the implicit conversion sequence.

std::initializer_list as template argument for constructor

Consider a class which inherits from a std container with a template constructor which calls the underlying constructor of the container. This template constructor works for the simple copy and move constructor but not for the initializer_list ctor.
template<typename container_T>
class test : public container_T {
public:
using container_type = container_T;
test() {}
// templated constructor
template<typename T>
test(T t)
: container_T(t) {}
// without this it won't compile
test(std::initializer_list<typename container_T::value_type> l)
: container_T(l) {}
};
int main() {
test<std::deque<int>> vdi1;
test<std::deque<int>> vdi2({1,2,3,4,5,6,7,8,9});
std::cout << "vdi2 before:" << std::endl;
for(auto it : vdi2)
std::cout << it << std::endl;
test<std::deque<int>> vdi3(std::move(vdi2));
std::cout << "vdi2 before:" << std::endl;
for(auto it : vdi2)
std::cout << it << std::endl;
std::cout << "vdi3 before:" << std::endl;
for(auto it : vdi3)
std::cout << it << std::endl;
return 0;
}
If I remove the initializer_list constructor vdi2 won't compile. So my question: Why is the initializer_list not deduced by the template constructor? And is it possible to do so?
why is the initializer_list not deduced by the templated constructor?
The reason is that {1,2,3,4,5,6,7,8,9} is just a synctatic construct that doesn't have a type. Therefore, the compiler cannot deduce a type T for this synctatic construct and the first constructor fails.
However, by special Standard rules std::initializer_list<T> (among other things) can be construct from this synctatic construct and T can be deduced to int. Hence the second constructor works.
By constrast with function template argument type deduction, with
auto x = {1,2,3,4,5,6,7,8,9};
the compiler sets the type of x to be std::initializer_list<int>. There are also special Standard rules that says it must be so. Strictly speaking this is not type deduction because, as said above, {1,2,3,4,5,6,7,8,9} doesn't have a type to be deduced. (The only type deduction happening here is T = int in std::initializer_list<T>.) Here the compiler chooses (it doesn't deduce) the type of x to be std::initializer_list<int>. In any case, there's no harm to use the abuse of language of saying that the type of x is deduced to std::initializer_list<int>.
Finally, as DyP said in the comments, what you probably want is inheriting all constructors (not only those taking one argument) from the base container class. You can do this by removing all the constructors that you currently have and add just this line to test:
using container_type::container_type;