The short answer is, you cannot make return f;
not move. When you return in C++, elision is default, and if not that it is moved, and if not that it is copied. If you use a non-trivial statement -- even true?v:v
, or static_cast<whatever const&>(v)
-- it will prevent auto-move and force a copy. But that won't help you.
Avoiding the move won't help you. The return object is still created within the function, and it is disposed of by the calling code.
Now, not all is lost. You can avoid this by the use of header files (which exist within the client code) doing the allocation, and a DLL-safe interface (to the implementation).
Here I design a sink
type, which sucks in data of type T
, in batches. It then calls some function pointer with a pvoid and is done.
template<class T>
struct sink {
void* s;
void(*)(void*, T const*, T const*) f;
void operator()(T const& t)const{ f(s, &t, (&t)+1); }
void operator()(std::initializer_list<T> il) {
f(s, il.begin(), il.end());
}
};
template<class T, class A>>
sink<T> vector_sink( std::vector<T, A>& out ) {
return {&out, +[](void* s, T const* b, T const* e){
auto* pout = static_cast<std::vector<T,A>*>(s);
pout->insert( pout->end(), b, e );
}};
}
now, export from the DLL:
void make_data(sink<int> s) {
s({1,2,3,4,5});
}
in a header file, expose:
void make_data(sink<int> s);
std::vector<int> return_vector() {
std::vector<int> r;
make_data( vector_sink(r) );
}
and now the vector
lives completely in the client code of the DLL. Only a standard layout class (consisting of 2 pointers) crosses the DLL barrier.
A fancier sink
could distinguish between rvalues and lvalues by just adding a new function (for move-data-in). However, that seems unwise if this is intended to bridge DLL boundaries.
This handles "returning" a vector. To take a vector in (without appending), I'd advise writing array_view<int>
which wraps two int*
s: similarly, standard layout that is pretty safe to cross DLL boundaries with.