Any

Published:

Problem

I often have the requirement to own object instances, while preserving polymorphic behavior (i.e. own the object and hold it by pointer, or reference).

This is usually expressed as a pointer (most of the times, std::unique_ptr), but when the pointer is stored in a std::container, client syntax becomes tricky (with - for example - vector iterators being dereferenced to a pointer - instead of a reference).

To avoid this, I need to implemented a polymorphic wrapper that able to store a heterogeneous collection.

The Basics

I know, many readers say that I know the solution. “void*”

So far in the Standard C++, you had not many options when it comes to holding variable types in a variable. Of course, you could use void*, yet this wasn’t super safe.

Potentially, void* could be wrapped in a class with some type discriminator.

class Any
{
    void*    mValue;
    TypeInfo mTypeInfo;
};

As you see, we have some basic form of the type, but it’s a bit of coding required to make sure “any” is type-safe.

std::any

The C++17 solve this problem with the std::any. It gives you a chance to store anything in an object, and it reports errors (or throw exceptions) when you’d like to access a type that is not active.

std::any a(12);

// set any value:
a = std::string("Hello!");
a = 16;

// we can read it as int
std::cout << std::any_cast<int>(a) << '\n'; 

// but not as string:
try 
{
    std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e) 
{
    std::cout << e.what() << '\n';
}

// reset and check if it contains any value:
a.reset();
if (!a.has_value())
{
    std::cout << "a is empty!" << "\n";
}

// you can use it in a container:
std::map<std::string, std::any> m;
m["integer"] = 10;
m["string"] = std::string("Hello World");
m["float"] = 1.0f;

for (auto &[key, val] : m)
{
    if (val.type() == typeid(int))
        std::cout << "int: " << std::any_cast<int>(val) << "\n";
    else if (val.type() == typeid(std::string))
        std::cout << "string: " << std::any_cast<std::string>(val) << "\n";
    else if (val.type() == typeid(float))
        std::cout << "float: " << std::any_cast<float>(val) << "\n";
}

The code will output:

16
bad any_cast
a is empty!
float: 1
int: 10
string: Hello World

It is pretty cool! The STD provide us the polymorphic wrapper capable of holding any type.

  • std::any is not a template class like std::optional or std::variant.
  • by default it contains no value, and you can check it via .has_value().
  • you can reset an any object via .reset().
  • it works on “decayed” types - so before assignment, initialization, emplacement the type is transformed by std::decay.
  • when a different type is assigned, then the active type is destroyed.
  • you can access the value by using std::any_cast, it will throw bad_any_cast if the active type is not T.
  • you can discover the active type by using .type() that returns std:: type_info of the type.

The only problem with the std::any is that it’s only available from C++17.

Solution

Implement our costume polymorphic wrapper capable of holding any type, based on the std::any and boost::any.

template<class T>
using StorageType = typename decay<typename remove_reference<T>::type>::type;

struct Any
{
    bool is_null() const { return !ptr; }
    bool not_null() const { return ptr; }

    template<typename U> Any(U&& value)
        : ptr(new Derived<StorageType<U>>(forward<U>(value)))
    {

    }

    template<class U> bool is() const
    {
        typedef StorageType<U> T;

        auto derived = dynamic_cast<Derived<T>*> (ptr);

        return derived;
    }

    template<class U>
    StorageType<U>& as()
    {
        typedef StorageType<U> T;

        auto derived = dynamic_cast<Derived<T>*> (ptr);

        if (!derived)
            throw bad_cast();

        return derived->value;
    }

    template<class U>
    operator U()
    {
        return as<StorageType<U>>();
    }

    Any()
        : ptr(nullptr)
    {

    }

    Any(Any& that)
        : ptr(that.clone())
    {

    }

    Any(Any&& that)
        : ptr(that.ptr)
    {
        that.ptr = nullptr;
    }

    Any(const Any& that)
        : ptr(that.clone())
    {

    }

    Any(const Any&& that)
        : ptr(that.clone())
    {

    }

    Any& operator=(const Any& a)
    {
        if (ptr == a.ptr)
            return *this;

        auto old_ptr = ptr;

        ptr = a.clone();

        if (old_ptr)
            delete old_ptr;

        return *this;
    }

    Any& operator=(Any&& a)
    {
        if (ptr == a.ptr)
            return *this;

        swap(ptr, a.ptr);

        return *this;
    }

    ~Any()
    {
        if (ptr)
            delete ptr;
    }

private:
    struct Base
    {
        virtual ~Base() {}

        virtual Base* clone() const = 0;
    };

    template<typename T>
    struct Derived : Base
    {
        template<typename U> Derived(U&& value) : value(forward<U>(value)) { }

        T value;

        Base* clone() const { return new Derived<T>(value); }
    };

    Base* clone() const
    {
        if (ptr)
            return ptr->clone();
        else
            return nullptr;
    }

    Base* ptr;
};

Now as we can see, this is a polymorphic wrapper capable of holding any type. Moreover, this is useful when you want to store a heterogeneous collection, such as vector.

Let’s test it!

int main()
{
    Any n;
    assert(n.is_null());

    string s1 = "foo";

    Any a1 = s1;

    assert(a1.not_null());
    assert(a1.is<string>());
    assert(!a1.is<int>());

    Any a2(a1);

    assert(a2.not_null());
    assert(a2.is<string>());
    assert(!a2.is<int>());

    string s2 = a2;

    assert(s1 == s2);
}

My conclusion that try to use the Standard Library rather than rolling a custom implementation. You are avoid code maintance and headache. However the costum any class is a perfect approach when you can not use C++17, but you have to use the polymorphic wrapper.

Thank you for reading!