I am trying to simulate std::any
, my idea is to use the base class pointer to point to different types of template derived classes, to implement the function of storing different types of data, like std::any
; thus I wrote the following code:
class Any {
TypeBase *_ptr;
public:
Any(): _ptr(nullptr) {}
Any(const Any &other): Any() { _ptr = other._ptr->copy(); }
Any(Any&& _other): Any() {
_ptr = _other._ptr;
_other._ptr = nullptr;
}
template<typename T> Any(T&& _data): Any() {
/*
* this function attempts to capture rvalue param
*/
_ptr = new Container<T>(std::forward<T>(_data));
}
~Any() { delete _ptr; }
template<typename T> T& data_cast() const {
if (_ptr == nullptr)
throw std::runtime_error("Container empty");
return static_cast<Container<T>*>(_ptr)->data;
}
};
And those toolkits class is:
struct TypeBase {
virtual ~TypeBase() {}
virtual TypeBase* copy() = 0;
};
template<typename T> struct Container: public TypeBase {
T data; // those datas will store here
Container() = delete;
template<typename U> Container(U&& _data): data(std::forward<U>(_data)) {}
virtual ~Container() {}
virtual TypeBase* copy() override { return (new Container<T>(data)); }
};
My problem is that when I use the code like this, the resulting data is not stored in the Container
:
#include <stdexcept>
// paste the definition given above
int main()
{
double y = 13.32;
Any a = y;
std::cout << "The data of 'a' is: " << a.data_cast<double>() << std::endl;
/* it doesn't work here, only outputs some wrong number */
return 0;
}
Here's what I understand: the compiler implicitly constructs an "Any" object for me using y
, and then calls the copy constructor to initialize the a
.
What I expected is that "Any" would accept both lvalue and rvalue data to reduce the copy overhead, but apparently there was something wrong with the way I was writing it that I wasn't aware of.
I tried to use the debugger to trace the process and found that the function call process did exactly what I expected. Even though I can see the pointer in a
is valid, the output is still strange: The data of a is: 3.10818e-317
.
I can hardly get to the root of the problem, so I want someone to guide me, or maybe I just wrote the wrong code and someone can point it out.
You're constructing a Container<double&>
but later casting a pointer to it to Container<double>*
.
When you pass an lvalue to a forwarding reference, the template parameter gets deduced as a reference type. That is, in
template<typename T> Any(T&& _data): Any() {
_ptr = new Container<T>(std::forward<T>(_data));
}
When you pass the lvalue y
, T
gets deduced to be double&
. That way the type of _data
is double& &&
, which collapses to double&
. If T
were double
then _data
would be a double&&
, which couldn't bind to an lvalue.
The solution is pretty simple: just apply std::remove_reference_t
:
template <typename T>
Any(T&& data) : Any() {
using DataT = std::remove_reference_t<T>;
_ptr = new Container<DataT>(std::forward<T>(data));
}