Я обнаружил случай, когда clang 8.x не удаляет копию объекта шаблонного класса, с которым gcc и msvc не имеют проблем. В моем реальном приложении эта лишняя копия довольно дорогая, поэтому я пытаюсь разобраться в этом и в итоге лучше понять, когда выбрано копирование и не выполняется в C ++ 17.
Проблема показана в фрагменте кода ниже. Функция, объявленная с типом автоматического возврата, который возвращает именованный объект класса, имеет дополнительную конструкцию копирования в своем теле. Если возвращение перекодируется для возврата неназванного временного объекта, происходит исключение. Если функция перекодируется для явного возврата экземпляра класса (вместо auto), происходит исключение.
Если структура A не имеет параметра шаблона, генерируется также полностью элидируемый код.
Проблема показывает, является ли все, кроме или нет, встроенным (NOINLINE, чтобы вы могли видеть проблему в Godbolt, не выполняя код).
// compiled with -O2 -std=c++17
#if defined(_MSC_VER) && !defined(__clang__)
#define NOINLINE __declspec(noinline)
#else
#define NOINLINE __attribute__((noinline))
#endif
template<int P>
struct A {
int data = 0;
NOINLINE explicit A(int data_) noexcept : data(data_) { }
NOINLINE ~A() noexcept { }
NOINLINE A(const A& other) noexcept : data(other.data) { }
};
template <int P>
NOINLINE auto return_auto_A_nrvo(const A<P>& a) noexcept {
/* clang 6.0 thru 8.0 doesn't elide copy of 'result':
gcc and msvc elide the copy as expected.
mov r14, rsp
mov rdi, r14
call A<0>::A(A<0> const&)
mov rdi, rbx
mov rsi, r14
call A<0>::A(A<0> const&)
mov rdi, r14
call A<0>::~A() [base object destructor]
* return A<P>(a); is fully optimized
*/
A<P> result(a);
return result;
}
template <int P>
NOINLINE A<P> return_A_nrvo(const A<P>& a) noexcept {
// NRVO with explicit return type: fully optimized
A<P> result(a);
return result;
}
template <int P>
NOINLINE auto return_auto_A_rvo(const A<P>& a) noexcept {
// RVO: fully optimized
return A<P>(a);
}
NOINLINE int main() {
auto a1 = A<1>(42);
auto a2 = return_auto_A_nrvo(a1);
auto a3 = return_A_nrvo(a1);
auto a4 = return_auto_A_rvo(a1);
return a2.data + a3.data + a4.data;
}
Комментарии в функции return_auto_A_nrvo () показывают код, сгенерированный clang с неопубликованной копией. Все остальные варианты генерируют полностью проверенный код. Копия также удаляется, если у класса А нет параметров шаблона.
Эта ссылка на Godbolt показывает код, сгенерированный GCC, clang и msvc: https://www.godbolt.org/z/FDAvQO.
Возможно, это просто ошибка / пропущенная возможность оптимизации, которую упускает кланг, а бренды G и M - нет. Если это так, я постараюсь найти подходящее место, чтобы опубликовать это, чтобы люди из Clang исправили это. Но я чувствую, что здесь может происходить что-то более глубокое, например, фундаментальная разница между возвращением auto и возвращением шаблонного объекта класса. Я полагаю, что C ++ 17 гарантирует, что безымянный-RVO всегда будет происходить, но этот-RVO, как в моем случае, не гарантирован - я хотел бы понять, почему это так (и почему это применимо здесь).