Adding C++17 structured bindings support to your classes
Introduction
C++17 adds structured bindings (proposals here and here) to the language, which allow you to declare multiple variables initialised from a tuple-like object:
This is a very powerful and expressive feature, but the most interesting element for me is the ability to add support for this to your own classes. This post is a short tutorial on how to do this, mostly for my own future reference.
Built-in support
The great news is that structured bindings are supported out-of-the-box for classes where all the non-static member variables are public (or all public-only non-statc members are in a single direct base class). So a class like this can be decomposed with no additional code:
So can this:
If you have more complex classes, or want to wrap/process members before exposing them, you’ll need to add support yourself. Fortunately, this is rather elegantly built on top of existing mechanisms. All you need to do is tell the compiler how many variables you want to expose, the types of them, and how to get at the values. This is done through the std::tuple_size
, std::tuple_element
, and get
utilities.
Supporting other classes
For demonstration purposes we’ll write a small class named Config
, which stores some immutable configuration data. We’ll be returning name
as a C++17 std::string_view
, id
by value, and data
by reference to const.
The simplest specialization is std::tuple_size
. Since there are three elements, we’ll just return 3
.
Next is get
. We’ll use C++17’s if constexpr
for brevity. I’ve just added this as a member function to avoid the headache of template friends, but you can also have it as a non-member function found through ADL.
Finally we need to specialize std::tuple_element
. For this we just need to return the type corresponding to the index passed in, so std::string_view
for 0
, std::size_t
for 1
, and const std::vector<std::string>&
for 2
. We’ll cheat and get the compiler to work out the types for us using the get
function we wrote above. This way, we don’t need to touch this specialization if we want to change the types we return later, or want to add more variables to the class.
You could do this the long way if you aren’t comfortable with the decltype
magic:
With all of that done, we can now decompose Config
like so:
Let me know what you think of this article on twitter @TartanLlama or leave a comment below!