Может ли static_cast использовать один и тот же тип для выполнения служебных задач во время выполнения?
У меня есть шаблон структуры, который принимает два типа (T
и S
), и в какой-то момент использует static_cast
для преобразования из одного типа в другой. Часто бывает, что T
и S
являются одним и тем же типом.
Упрощенный пример настройки:
template <typename T, typename S = T>
struct foo
{
void bar(T val)
{
/* ... */
some_other_function(static_cast<S>(val));
/* ... */
}
};
В случае, когда S
является тем же классом, что и T
, имеет или может static_cast
вводить дополнительные служебные данные, или это нулевая операция, которая всегда будет игнорироваться?
Если он вводит накладные расходы, есть ли простой трюк метапрограммирования шаблона, чтобы выполнить static_cast
только при необходимости, или мне нужно создать частичную специализацию, чтобы справиться с тегом T == S
? Я предпочел бы избежать частичной специализации всего шаблона foo
, если это возможно.
Ответы
Ответ 1
Да, это возможно.
Вот пример:
struct A {
A( A const& ) {
std::cout << "expensive copy\n";
}
};
template<typename T>
void noop( T const& ) {}
template <typename T, typename S = T>
void bar(T val)
{
noop(static_cast<S>(val));
}
template <typename T>
void bar2(T val)
{
noop(val);
}
int main() {
std::cout << "start\n";
A a;
std::cout << "bar2\n";
bar2(a); // one expensive copy
std::cout << "bar\n";
bar(a); // two expensive copies
std::cout << "done";
}
в принципе, static_cast
может вызвать вызов конструктора копии.
Для некоторых типов (например, int
) конструктор копирования в основном свободен, и компилятор может его устранить.
Для других типов он не может. В этом контексте исключение для копирования также не является законным: если у вашего конструктора копии есть побочные эффекты или компилятор не может доказать, что он не имеет побочных эффектов (общий, если конструктор копирования нетривиален), он будет вызываться.
Ответ 2
Чтобы дополнить ответ Якка, я решил опубликовать сборку, чтобы подтвердить это. Я использовал std::string
как тип теста.
foo<std::string>.bar()
- Без литья
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movq %rcx, 16(%rbp)
movq %rdx, 24(%rbp)
movq 24(%rbp), %rax
movq %rax, %rcx
call _Z19some_other_functionRKSs
nop
addq $32, %rsp
popq %rbp
ret
foo<std::string>.bar()
- static_cast<T>()
pushq %rbp
pushq %rbx
subq $56, %rsp
leaq 128(%rsp), %rbp
movq %rcx, -48(%rbp)
movq %rdx, -40(%rbp)
movq -40(%rbp), %rdx
leaq -96(%rbp), %rax
movq %rax, %rcx
call _ZNSsC1ERKSs // std::string.string()
leaq -96(%rbp), %rax
movq %rax, %rcx
call _Z19some_other_functionRKSs
leaq -96(%rbp), %rax
movq %rax, %rcx
call _ZNSsD1Ev // std::string.~string()
jmp .L12
movq %rax, %rbx
leaq -96(%rbp), %rax
movq %rax, %rcx
call _ZNSsD1Ev // std::string.~string()
movq %rbx, %rax
movq %rax, %rcx
call _Unwind_Resume
nop
.L12:
addq $56, %rsp
popq %rbx
popq %rbp
ret
Этот код генерируется только с помощью -O0
. Любой уровень оптимизации будет даже устранять два случая.