Почему оптимизация rvalue не происходит в классах с конструктором с универсальными ссылочными аргументами?
http://coliru.stacked-crooked.com/a/672f10c129fe29a0
#include <iostream>
template<class ...ArgsIn>
struct C {
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";} // rvo occurs without &&
~C(){std::cout << "Dstr\n";}
};
template<class ...Args>
auto f(Args ... args) {
int i = 1;
return C<>(i, i, i);
}
int main() {
auto obj = f();
}
Выход:
Ctr
Ctr
Dstr
Ctr
Dstr
Dstr
Я считаю, что проблема заключается в том, что
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
не являются конструкторами копирования / перемещения в том, что касается языка, и поэтому вызовы к ним не могут быть исключены компилятором. Из §12.8 [class.copy] / p2-3, выделение добавлено, а примеры опущены:
не шаблонный конструктор для класса
X
является конструктором копирования, если
его первый параметр имеет типX&
,const X&
,volatile X&
или жеconst volatile X&
,
и либо нет других параметров, либо все
другие параметры имеют аргументы по умолчанию (8.3.6).не шаблонный конструктор для класса
X
является конструктором перемещения, если
его первый параметр имеет типX&&
,const X&&
,volatile X&&
, или же
const volatile X&&
и либо нет других параметров, либо все
другие параметры имеют аргументы по умолчанию (8.3.6).
Другими словами, конструктор, который является шаблоном, никогда не может быть конструктором копирования или перемещения.
Оптимизация возвращаемого значения — это особый случай исключения копирования, который описывается как (§12.8 [class.copy] / p31):
При соблюдении определенных критериев реализация может быть опущена
конструкция копирования / перемещения объекта класса, даже если конструктор
выбран для операции копирования / перемещения и / или деструктора для
Объект имеет побочные эффекты.
Это позволяет реализациям исключать «конструкцию копирования / перемещения»; создание объекта с использованием чего-либо, что не является ни конструктором копирования, ни конструктором перемещения, не является «конструкцией копирования / перемещения».
Так как C
имеет пользовательский деструктор, неявный конструктор перемещения не генерируется. Таким образом, разрешение перегрузки выберет шаблонный конструктор с Args
выводится как C
, что является лучшим соответствием, чем неявный конструктор копирования для значений r. Однако компилятор не может исключить вызовы этого конструктора, поскольку он имеет побочные эффекты и не является ни конструктором копирования, ни конструктором перемещения.
Если шаблонный конструктор вместо
template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";}
Тогда это не может быть реализовано с Args
знак равно C
создать конструктор копирования, так как это приведет к бесконечной рекурсии. В стандарте есть специальное правило, запрещающее такие конструкторы и реализации (§12.8 [class.copy] / p6):
Объявление конструктора для класса
X
плохо сформирован, если его
первый параметр имеет тип (необязательно cv-qualified)X
и либо
нет других параметров или все остальные параметры имеют
аргументы по умолчанию. Шаблон функции-члена никогда не создается
произвести такую подпись конструктора.
Таким образом, в этом случае единственным жизнеспособным конструктором был бы неявно определенный конструктор копирования, и вызовы этого конструктора могут быть исключены.
Если мы вместо Удалить пользовательский деструктор из C
и добавьте еще один класс, чтобы отслеживать, когда C
Вместо этого вызывается деструктор:
struct D {
~D() { std::cout << "D's Dstr\n"; }
};
template<class ...ArgsIn>
struct C {
template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}
D d;
};
Мы видим только один звонок D
деструктор, указывающий, что только один C
Объект построен. Вот C
Конструктор перемещения неявно генерируется и выбирается с помощью разрешения перегрузки, и вы снова видите, как RVO срабатывает.