std::any by std::exception_ptr
Asked Answered
O

2

10

Probably I am not the first person finding out that std::exception_ptr could be used to implement an any type (performance considerations being put aside), as it is probably the only type in C++ that can hold anything. Googling did not, however, bring any result in this direction.

Does anybody know whether the following approach has been used anywhere for anything useful?

#include <exception>
#include <iostream>

struct WrongTypeError : std::exception { };

class Any {
public:
   template <class T>
   void set (T t) {
      try { throw t; }
      catch (...) { m_contained = std::current_exception(); }
   }

   template <class T>
   T const & get () {
      try { std::rethrow_exception (m_contained); }
      catch (T const & t) { return t; }
      catch (...) { throw WrongTypeError {}; }
   }

private:
   std::exception_ptr m_contained = nullptr;
};

int main () {
    auto a = Any {};
    a.set (7);
    std::cout << a.get<int> () << std::endl;

    a.set (std::string {"Wonderful weather today"});
    std::cout <<  a.get<std::string> () << std::endl;
    return 0;
}
Overbite answered 21/12, 2015 at 8:13 Comment(3)
I've edited the code. I think it does not slice anymore.Overbite
g++ 4.8.4 does not have problems generating ten million Anys and storing them in a vector. It is, however, terribly slow. (30 seconds for setting and getting those 10 million values.)Overbite
Actually, I've always thought that a better trick (since it's available since C++98) would be to use std::locale, storing whatever you want as a facet.Limonene
B
5

as it is probably the only type in C++ that can hold anything.

I'm afraid this is not the case. boost::any can hold any type, and even copies (assume the type is copyable) it correctly as well. It is implemented (broadly speaking) using a base class and a templated child:

class any_base {
  ...
}

template <class T>
class any_holder : public any_base
{
 private:
  T m_data;
}

From this you can imagine that you can stuff any type into an any_holder (with the right interface), and then you can hold an any_holder by pointer to any_base. This technique is a type of type erasure; once we have an any_base pointer we are holding an object but don't know anything about the type. You could say this is total type erasure, something like std::function provides partial type erasure (and may use similar techniques under the hood, I'm not sure off the top of my head).

boost::any provides additional interface to support its usage of holding any type, and it probably provides better performance as throwing exceptions is crazy slow. Also, as I mentioned before, it correctly copies the underlying object, which is pretty cool. exception_ptr is a shared ownership pointer, so I believe it makes shallow copies instead.

Boost any website: http://www.boost.org/doc/libs/1_59_0/doc/html/any.html

It's being considered for the standard I believe: http://en.cppreference.com/w/cpp/experimental/any

It seems like the implementation is similar to boost but adds a small object optimization.

exception_ptr is quite a strange beast as far as I can tell, I've come across it before and googled it and there's surprisingly little information out there. I'm pretty sure however that it's magical, i.e. it cannot be implemented in userspace. I say this because when you throw it, the type seems to magically unerase itself, this isn't generally possible.

Boson answered 21/12, 2015 at 8:37 Comment(1)
That's how I made my 'any', although actually the mechanism is simple enough that it can be implemented using just a void* and a const type_info&, however then you have to store the destructor if you want proper cleanup.Averill
B
4

You are certainly the first person I have come across who has thought of it.

I'm definitely impressed by your lateral thinking skills :)

There is a problem with this approach however, (other than the obvious performance problem).

This stems from the fact that throw is permitted to make a copy of the object thrown.

Firstly this places a restriction on what you may store in your 'any' class, secondly it will have further performance implications and thirdly, each time you access your object, the compiler is not obliged to give you the same one. It's allowed to give you a copy. This means that at the very least you should only store immutable objects this way. (Note: when I say 'should' I really mean 'absolutely should not!') :)

You could get around this by crafting a class that allocates memory to store the object, records it's type and deletes it properly... But if you did that you'd be better off without the exception complication anyway.

In any case, this is what boost::any does under the covers.

Bieber answered 21/12, 2015 at 9:24 Comment(1)
"I'm definitely impressed by your lateral thinking skills" -- I can't upvote twice, can I?Overbite

© 2022 - 2024 — McMap. All rights reserved.