Конструктор преобразования требует перемещения, но не копирования

Используя этот код:

template <class T> class Test {
T _temp;

public:
Test() {
std::cout << "Test()" << std::endl;
};

template <class T2> Test(Test<T2> const &test) {
std::cout << "template <class T2> Test(Test<T2> const &test)" << std::endl;
};

template <class T2> Test(Test<T2> &&test) {
std::cout << "template <class T2> Test(Test<T2> &&test)" << std::endl;
};

};

С этим тестовым кодом:

Test<int> testInt;
Test<float> testFloat(testInt);
Test<float> testFloat2(std::move(testInt));

std::cout << "----------" << std::endl;

Test<int> testInt2;
Test<int> testInt3(testInt2);
Test<int> testInt4(std::move(testInt2));

Производит этот вывод:

Test()
template <class T2> Test(Test<T2> const &test)
template <class T2> Test(Test<T2> &&test)
----------
Test()

Конструкторы копирования и перемещения по умолчанию используются вместо конструкторов преобразования при использовании одних и тех же типов.

Но если я добавлю конструктор копирования по умолчанию в классе:

Test(Test const &test) = default;

Он производит этот вывод:

Test()
template <class T2> Test(Test<T2> const &test)
template <class T2> Test(Test<T2> &&test)
----------
Test()
template <class T2> Test(Test<T2> &&test)

Конструктор преобразования хода вызывается даже с теми же типами, почему?

Есть ли способ унифицировать конструктор копирования и преобразования, чтобы избежать дублирования кода?

1

Решение

Правила добавления неявно сгенерированных (= сгенерированных компилятором) конструкторов перемещения довольно консервативны: добавляйте только одно, если это безопасно. Если конструктор копирования был определенный пользователем, компилятор не может предположить, что добавление какого-либо простого сгенерированного компилятором конструктора перемещения все еще безопасно, поэтому конструктор перемещения не будет добавлен. На самом деле, достаточно объявлять это предотвращает генерацию конструктора перемещения, IIRC разрешает типы только для копирования.

Шаблоны конструктора никогда не считаются ни копирующими, ни конструкторами перемещения, поэтому они не предотвращают их неявную генерацию.

Конечно, если конструктор перемещения никогда не объявляется, вместо разрешения перегрузки можно выбрать другой конструктор. Рассмотрим этот пример:

#include <iostream>
#include <utility>

struct loud
{
loud() { std::cout << "default ctor\n"; }
loud(loud const&) { std::cout << "copy ctor\n"; }
loud(loud&&) { std::cout << "move ctor\n"; }
};

struct foo
{
loud l;
};

struct bar
{
loud l;
bar() = default;
bar(bar const&) = default;
};

int main()
{
foo f0;
foo f1(f0);
foo f2(std::move(f0));

std::cout << "--------------\n";

bar b0;
bar b1(b0);
bar b2(std::move(b0));
}

Выход:

ctor по умолчанию
копировать ctor
переместить ctor
--------------
ctor по умолчанию
копировать ctor
копировать ctor

Вот, foo получит неявно объявленные конструкторы по умолчанию, копирование и перемещение, тогда как bar не получит неявно объявленный конструктор перемещения, так как он имеет объявленный пользователем конструктор копирования.


Во втором случае нет неявно объявленного конструктора перемещения, поэтому разрешение перегрузки для Test<int> testInt4(std::move(testInt2)) предпочитает шаблон конструктора копирующему конструктору, поскольку первый использует ссылку, которая менее квалифицирована по cv.


Чтобы уменьшить дублирование кода, вы можете делегировать конструкцию:

template <class T>
class Test {
T _temp;

struct tag {};

public:
Test() {
std::cout << "Test()" << std::endl;
};

Test(Test const& test)
: Test(test, tag{})
{}

Test(Test&& test)
: Test(std::move(test), tag{})
{}

template <class T2> Test(Test<T2> const &test, tag = {}) {
std::cout << "template <class T2> Test(Test<T2> const &test)" << std::endl;
};

template <class T2> Test(Test<T2> &&test, tag = {}) {
std::cout << "template <class T2> Test(Test<T2> &&test)" << std::endl;
};

};

Кстати, вы также можете уменьшить количество шаблонов, используя отлично пересылаемый шаблон конструктора вместо двух шаблонов конструктора:

Используя эту черту:

template<class T>
struct is_Test : std::false_type {};
template<class T>
struct is_Test<Test<T>> : std::true_type {};

Вы можете определить:

    template <class T2,
class = typename std::enable_if<is_Test<T2>::value>::type>
Test(T2&& test, tag = {}) {
std::cout << "template <class T2> Test(T2&& test)" << std::endl;
};
2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]