This article introduces the new std::any class in C++ 17. It can store all values that are “Copy Constructible”. In the following we will first introduce the basic usage of std::any, and then describe the practical use of it.

Usage

The use of the std::any class is broken down as follows.

  • Constructed objects
  • Assigning operators
  • any_cast function
  • has_value member function
  • reset member function
  • emplace member function
  • type member function

Constructed objects

The std::any category is defined in the <any> header file. There are several ways of constructing std::any objects.

1
2
3
4
5
6
7
#include <any>

int main() {
  std::any a;  // #1
  std::any i(1);  // #2
  std::any c = std::make_any<double>(1.0);  // #3
}
  1. construct a std::any object without a value using the Default Constructor.
  2. pass the value to the std::any constructor so that it stores the value.
  3. construct a std::any object “with the value of ValueType” with the std::make_any<ValueType> function. The arguments to std::make_any are passed to the constructors of ValueType.

Regardless of how the std::any object is constructed, the value stored must be Copy Constructible. For example, if we try to pass a non-copyable std::unique_ptr object into the std::any construct, we get the following compilation error.

1
2
3
4
5
6
#include <any>
#include <memory>

int main() {
  std::any a(std::make_unique<int>(5));  // Compilation error.
}

Assigning operators

We can replace the values stored in std::any by assigning operators. The type of value can also be changed at will.

1
2
3
4
5
6
7
8
#include <any>

int main() {
  std::any x;
  x = 1;
  x = "hello world";
  x = 42.0;
}

We can also assign one std::any object to another std::any object.

1
2
3
4
5
6
7
#include <any>

int main() {
  std::any x(42);
  std::any y;
  y = x;
}

any_cast function

The values stored by std::any must be read in the std::any_cast style function. Because we must pass in the stencil argument of std::any_cast, we must know the type of the value stored in std::any. If the type of the template parameter is different from the type of the std::any stored value, std::any_cast will throw the following exception to std::bad_any_cast.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <any>
#include <iostream>

int main() {
  std::any x(42);

  std::cout << std::any_cast<int>(x) << std::endl;

  try {
    std::cout << std::any_cast<double>(x) << std::endl;
  } catch (std::bad_any_cast &exc) {
    std::cout << "exception: " << exc.what() << std::endl;
  }
}

If the std::any_cast object passed to std::any_cast is a reference type, std::any_cast will return a copy of the stored value.

If we want direct access to the values in the std::any object, the argument type of std::any_cast must be std::any * (pointer type). Example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <any>
#include <iostream>

int main() {
  std::any x(42);

  int &i = *std::any_cast<int>(&x);
  std::cout << i << std::endl;  // Prints 42

  ++i;
  std::cout << std::any_cast<int>(x) << std::endl;  // Prints 43

#if 0
  int &j = std::any_cast<int>(x);  // Compilation error.
  std::cout << j << std::endl;
#endif
}

has_value member function

The has_value member function returns whether std::any has the value.

1
2
3
4
5
6
7
8
9
#include <any>
#include <iostream>

int main() {
  std::any x(42);
  std::any y;
  std::cout << "x.has_value(): " << x.has_value() << std::endl;  // 1
  std::cout << "y.has_value(): " << y.has_value() << std::endl;  // 0
}

reset member function

The reset member function clears the values stored in std::any.

1
2
3
4
5
6
7
8
9
#include <any>
#include <iostream>

int main() {
  std::any x(42);
  std::cout << "before: " << x.has_value() << std::endl;  // 1
  x.reset();
  std::cout << "after: " << x.has_value() << std::endl;  // 0
}

emplace member function

emplace<ValueType>(...) The member function constructs a value of type ValueType within the std::any object. The argument of emplace becomes the argument of the ValueType construct. If the std::any object already has a value, the existing value will be deconstructed first.

1
2
3
4
5
6
7
8
9
#include <any>
#include <complex>
#include <iostream>

int main() {
  std::any x(42);
  x.emplace<std::complex<double>>(0, 1);
  std::cout << std::any_cast<std::complex<double>>(x) << std::endl;
}

Although emplace<ValueType>(...) and operator=(ValueType(...)) is similar to operator=(ValueType(...), but if the Copy Constructor (or Move Constructor) of ValueType has to take longer to execute, emplace<ValueType>(...) can save us the execution time of copying (or moving) objects from a ValueType temporary object.

type member function

The type member function returns Run-Time Type Information (RTTI) for a numeric type. The type member function and the typeid keyword are used to select the appropriate std::any_cast function template parameter for the run-time. If the std::any object has no value, the type member function will return typeid(void).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <any>
#include <iostream>
#include <typeinfo>

void print(const std::any &x) {
  if (x.type() == typeid(int)) {
    std::cout << "int: " << std::any_cast<int>(x) << std::endl;
  } else if (x.type() == typeid(double)) {
    std::cout << "double: " << std::any_cast<double>(x) << std::endl;
  } else if (x.type() == typeid(void)) {
    std::cout << "no value" << std::endl;
  } else {
    std::cout << "unhandled type: " << x.type().name() << std::endl;
  }
}

int main() {
  print(std::any(42));
  print(std::any(1.0));
  print(std::any());
}

Context of use

When designing APIs, we often have to keep their Context Object for the Callback function. The Context Object is then passed to the Callback function when it is called. However, we run into two problems at this point:

  • The party holding the Context Object does not want to impose too many restrictions on the Context Object, nor does it want to know the type of the Context Object.
  • The party holding the Context Object must know how to deconstruct the Context Object.

The class std::any solves these two problems elegantly. A simple example is the following.

 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
47
48
49
50
51
52
53
54
#include <any>
#include <iostream>
#include <utility>
#include <vector>

// File 1: event_source.cpp
class EventSource {
public:
  using EventListener = void (*)(std::any &);

  void addEventListener(EventListener callback, std::any context) {
    listeners_.push_back(std::make_pair(callback, std::move(context)));
  }

  void dispatchEvent() {
    for (auto &&listener : listeners_) {
      listener.first(listener.second);
    }
  }

private:
  std::vector<std::pair<EventListener, std::any>> listeners_;
};

// File 2: callback1.cpp
void callback1(std::any &context_any) {
  int &ctx = *std::any_cast<int>(&context_any);
  std::cout << "callback1: " << ctx++ << std::endl;
}

void attach1(EventSource &source) {
  source.addEventListener(callback1, std::any(0));
}

// File 3: callback3.cpp
void callback2(std::any &context_any) {
  double &ctx = *std::any_cast<double>(&context_any);
  ctx *= 1.2;
  std::cout << "callback2: " << ctx << std::endl;
}

void attach2(EventSource &source) {
  source.addEventListener(callback2, std::any(1.0));
}

// File 4: main.cpp
int main() {
  EventSource source;
  attach1(source);
  attach2(source);

  source.dispatchEvent();
  source.dispatchEvent();
}

In practice, EventSource, callback1 and callback2 will belong to separate C++ source files. The EventSource does not need to know the actual type of the Context Object. Each Callback will obtain its own Context Object via std::any_cast according to its own logic after getting the std::any object.

Reference