Home > Software engineering >  Conversion constructor strangely not competing with copy constructor
Conversion constructor strangely not competing with copy constructor

Time:12-07

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?

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)
  • Related