It does mean a thing

Some notes on software and beyond

Any Struggles

In C++ you can’t just forget about types. Each variable has its own type that is not going to be changed by any means. What if you really need something heterogeneous? There is a known idiom called Any that enables you to erase the type and recall it later.

Let’s consider the following example:

1
2
3
4
5
6
// we forget the type of 3.0 which is double
any x = erase_type(3.0);
// now we obtain the value (but we have to provide the type)
double xd = recall_type<double>(x);
// what if we treat it as bool? won't work - throws exception
bool xb = recall_type<bool>();

Doesn’t it look useful? Unfortunately, there is no notion of Any neither in the C++11 nor the C++14 standard library. Although there is no reason to not use it (and it is widely used) so let’s consider two available non-standard implementations: the first one is Boost.Any and the other one is Any by Christopher Diggins.

If you glance over the code of these implementations you would definitely notice that both implementations follow some similar pattern that involves templated descendant of some non-templated base class (this implements type erasure). This resolves two major issues:

  • How to make cast from any to some T safe – i.e. how to check if the cast type is correct. Being able to access effective type the templated class can definitely provide all the information for such checks.

  • How to copy, move and access the value. The templated class has the type in its scope so this is quite easy.

Nevertheless, they are a bit different in details. Let’s consider the following snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class placeholder
{
  public:
      virtual const std::type_info& type() const = 0;
};

template <typename T>
class holder : public placeholder
{
  public:
      virtual const std::type_info& type() const
      {
          return typeid(T);
      }
      T held;
};

class any
{
  public:
      template <typename T>
      T as()
      {
          if (content->type() == typeid(T))
          {
              return static_cast<holder<T>*>(content)->held;
          }
          // else error
      }
  private:
      placeholder* content;
};

This illustrates the approach of Boost.Any. You may notice that the implementation always involves typeid (a part of RTTI) for safe casts, which could affect performance. Can we get any better? Actually yes: Christopher Diggins has made an improvement over this approach. His implementation of Any can be described with the following structure of classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class base_policy
{
  public:
      // declare some virtual fancy tricks 
      // to handle void** 
};

template <typename T>
class policy : public base_policy
{
  public:
      // implementations of virtual member 
      // functions that treat void** as T
};

namespace
{
  template <typename T>
  base_policy* selectPolicy()
  {
      static policy<T> p;
      return &p;
  }
}

class any
{
  public:
      template <typename T>
      any(const T& value) : policy(selectPolicy<T>())
      {
      }
      
      template <typename T>
      T as() const
      {
          if (selectPolicy<T>() == policy)
          {
              // reinterpret_cast to obtain value from void*
          }
          // else error
      }
  private:
      void* content;
      base_policy* policy;
};

Essentially, the idea is to keep all used policies online as static objects so we can easily compare them. Policy for any type T stays the same during the runtime (up to pointer) so it is quite easy to check whether the user tries to cast to the correct type.

Performance

The claim that really needs some support is: boost::any is not that fast as cdiggins::any. Obviously, typeid should affect performance but it is totally unclear how significant the difference is.

I didn’t pretend to do any thorough research on that – so just a simple benchmark made with Hayai looks like that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "cdiggins_any.hpp"
#include "hayai.hpp"
#include <boost/any.hpp>
#include <string.h>

const int REPEATS = 1000;
const int RUNS = 1000;

BENCHMARK(Any, Boost, RUNS, REPEATS)
{
  std::pair<int, int> pair;
  boost::any any(pair);
  any = boost::any_cast<std::pair<int, int>>(any);
}

BENCHMARK(Any, CDiggins, RUNS, REPEATS)
{
  std::pair<int, int> pair;
  cdiggins::any any(pair);
  any = any.cast<std::pair<int, int>>();
}

int main()
{
  hayai::ConsoleOutputter outputter;
  hayai::Benchmarker::AddOutputter(outputter);
  hayai::Benchmarker::RunAllTests();
  return 0;
}

What I got on my machine (i5, Ubuntu 14.04, GCC 4.8.2 and -std=c++11 -O3) is:

1
2
3
(1000 runs, 1000 iterations per run)
Boost.Any average time: 132.412 us
cdiggins::any average time: 104.735 us

This doesn’t look significant but still we now know the difference ;) I shared the benchmark code on github so please try it on your own!

Reliability

I bet there is no known trivial case which would lead Boost.Any to failure. But what about cdiggins::any? Unfortunately I’ve found one major issue.

Say we have two separate compiled libraries (Foo and Bar, yeah) which interchange data in the form of Any. Will policies of some type T from Foo and T from Bar match? The answer is No. These policies are different objects – this makes it totally impossible to safely cast the Any instance back to T.

Possible solution for this problem can be rather straightforward – combination of both approaches. The ‘equality miss’ case should not happen often in real apps so we can simply add a fallback procedure, which checks if types are really different in the way Boost does it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  template <typename T>
  bool fallbackValidType() const
  {
      return typeid(T) == policy->type();
  }

  template <typename T>
  inline bool validType() const
  {
      return (selectPolicy<T>() == policy) || fallbackValidType();
  }

  template <typename T>
  T as() const
  {
      if (validType<T>())
      {
          // cast
      }
      // else error
  }

I am pretty sure some things are disputable or unclear so I’d welcome any comments!

Comments