Search code examples
c++templatesc++17stdtemplate-meta-programming

How do I fix my `Any` class code to output data correctly?


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.


Solution

  • 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));
    }
    

    Demo