Reputation: 115
This has been bothering me for some time. Say we have a constructor that takes many parameters (copies or rvalues):
Object(A a, B b, C c, D d, ...);
When you need to use this constructor you can do it like this:
Object obj{{A params...}, {B params}, ...};
Which is great optimization-wise, since no copy and move constructors are called.
However, it can be hard to read at times. Also, perhaps A can be instantiated here but B and C will be passed as a copy. Therefore it makes sense to re-write it like this:
A a{A params...};
B b{B params...};
...
Object obj{a, b, ...}; // much nicer, but copy constructors are called
The problem is, with the latter approach, a copy constructor gets invoked for each parameter. This theoretically isn't a problem, since we can use std::move
on temporary parameters:
A a{A params...};
B b{B params...};
...
Object obj{std::move(a), std::move(b), ...} // no copy!
But here, we get move constructors invoked.
Is there some way to enforce copy elision in such case - declare the variables but have the output like in the first case? It's seems like a super simple optimization compiler could do but even -O3 doesn't fix it.
EDIT:
After some trial and error I concluded this can be solved using template constructor with universal reference parameters:
#include <iostream>
struct A{
A(){
std::cout << "A - default\n";
}
A(A &&a){
std::cout << "A - move\n";
}
A(A &a){
std::cout << "A - copy\n";
}
};
struct B{
A a_;
template <typename T>
// Not sure if std::forward is needed here.
explicit B(T &&a): a_(std::move(std::forward<T>(a))){
}
static B BCopyElision(){
return B{A{}};
}
static B BCopy(){
A a{};
return B(std::move(a));
}
static B BMove(){
A a{};
return B{std::move(a)};
}
};
int main()
{
B::BCopyElision();
// A - default;
// A - move;
B::BCopy();
// A - default;
// A - move;
B::BMove();
// A - default
// A - move;
return 0;
}
Is there a way to achieve this without (unnecessary) template constructors?
Upvotes: 0
Views: 65