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;
}
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?
CodePudding user response:
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)