Conversion constructor strangely not competing with copy constructor

Conversion constructor strangely not competing with copy constructor

Problem Description:

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?

Solution – 1

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)
Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject