Purpose
Making a wrapper that keeps reference to some constructor arguments.
That’s OK as long as the wrapper lifetime is longer or equal to its construction arguments.
If the wrapper is created from temporary objects, it should better be used in the same line _or_ it will make reference to possibly destroyed objects.
Example
#include <iostream>
#include <string>
using namespace std::literals;
struct Object {
std::string name;
Object(std::string n) : name{n} { std::cout << "Created " << name << std::endl; }
~Object() { std::cout << "Destroyed " << name << std::endl; name = "**"s + name + "**"s;}
Object(const Object& rhs) : Object{ "copy of "s + rhs.name } { }
Object& operator=(const Object& rhs) {
name = "copy of"s + rhs.name;
std::cout << "Copied " << rhs.name << std::endl;
return *this;
}
};
template <typename T>
struct Call {
const T& obj;
Call(const T& objIn) : obj{objIn} { }
void handle() {
std::cout << "handle call to " << obj.name << std::endl;
}
};
template <typename T>
auto makeCall(const T& obj)
{
return Call{ obj };
}
int main()
{
Object x { "constructed" };
std::cout << "*** auto cx = Call<Object>{x}" << std::endl;
auto cx = Call<Object>{x};
cx.handle();
std::cout << "*** auto cx2 = makeCall(x)" << std::endl;
auto cx2 = makeCall(x);
cx2.handle();
std::cout << "*** auto ct2 = makeCall( Object{\"temporary2\"} )" << std::endl;
auto ct2 = makeCall( Object{"temporary2"} );
ct2.handle();
}
Langage du code : PHP (php)
Available on Coliru
Output
Created constructed *** auto cx = Call<Object>{x} handle call to constructed *** auto cx2 = makeCall(x) handle call to constructed *** auto ct2 = makeCall( Object{"temporary2"} ) Created temporary2 Destroyed temporary2 handle call to **temporary2** Destroyed constructed
Solution
Make an extra layer of wrapper that would handle the cases where the wrapper is constructed from temporary objects.
First attempt
Replace the end (from makeCall() definition onwards) with
template <typename T>
struct Wrapped {
T obj;
Call<T> innerCall;
Wrapped(T&& tmp) : obj{tmp}, innerCall{obj} { }
void handle() {
std::cout << "wrapped call: ";
innerCall.handle();
}
};
template <typename T>
auto makeCall(T&& tmp)
{
return Wrapped<T>{ std::forward<T>(tmp) };
}
template <typename T>
auto makeCall(const T& obj)
{
return Call<T>{ obj };
}
int main()
{
Object x { "constructed" };
std::cout << "*** auto cx = Call<Object>{x}" << std::endl;
auto cx = Call<Object>{x};
cx.handle();
std::cout << "*** auto ct = Wrapped<Object>{ Object{\"temporary\"} }" << std::endl;
auto ct = Wrapped<Object>{ Object{"temporary"} };
ct.handle();
std::cout << "*** auto cx2 = makeCall(x)" << std::endl;
auto cx2 = makeCall(x);
cx2.handle();
std::cout << "*** auto ct2 = makeCall( Object{\"temporary2\"} )" << std::endl;
auto ct2 = makeCall( Object{"temporary2"} );
ct2.handle();
}
Langage du code : PHP (php)
See on Coliru
Output
Created constructed *** auto cx = Call<Object>{x} handle call to constructed *** auto ct = Wrapped<Object>{ Object{"temporary"} } Created temporary Created copy of temporary Destroyed temporary wrapped call: handle call to copy of temporary *** auto cx2 = makeCall(x) wrapped call: handle call to constructed *** auto ct2 = makeCall( Object{"temporary2"} ) Created temporary2 Created copy of temporary2 Destroyed temporary2 wrapped call: handle call to copy of temporary2 Destroyed copy of temporary2 Destroyed copy of temporary Destroyed constructed
More convoluted way
Replace the two makeCall() overloads with
template <typename T, bool needCopy = false>
struct CallSelected
{
using Type = Call<T>;
};
template <typename T>
struct CallSelected<T, true>
{
using Type = Wrapped<T>;
};
template <typename T>
struct CallSelector
{
using Type = typename CallSelected< std::decay_t<T>, std::is_rvalue_reference_v<T> >::Type;
};
template <typename T>
auto makeCall(T&& obj)
{
using Type = typename CallSelector<decltype(obj)>::Type;
return Type{ std::forward<T>(obj) };
}
Langage du code : PHP (php)
See also on Coliru, Produces the same output