Named tuple for C++

named tuple for cpp

When I use std::pair or std::tuple I always feel sad about using std::get to get results. Because this is not so readable, not so maintainable and a bit ugly. Even when you use first/second it’s easy to forget what was first argument of std::pair and so on. When you use tuple to pass more than 3 arguments situation becomes even worse. New standard has nothing in box to solve this, but we can fix this writing our own solution using C++14.

At MeetingCPP 2015 conference I attended nice presentation by and found great solution there – named tuple. After some playtime I made my own implementation draft which you can find in this post.

Ok, once again – what’s else wrong with std::tuple?

Look at standard example from cppreference.com:

std::tuple get_student(int id){ if (id == 0) return std::make_tuple(3.8, ‘A’, “Lisa Simpson”); if (id == 1) return std::make_tuple(2.9, ‘C’, “Milhouse Van Houten”); if (id == 2) return std::make_tuple(1.7, ‘D’, “Ralph Wiggum”); throw std::invalid_argument(“id”);} int main(){ auto student0 = get_student(0); std::cout << "ID: 0, " << "GPA: " << std::get(student0) << ", " << "grade: " << std::get(student0) << ", " << "name: " << std::get(student0) << '\n';}std::tuple get_student(int id)    if (id == 0) return std::make_tuple(3.8, ‘A’, “Lisa Simpson”);    if (id == 1) return std::make_tuple(2.9, ‘C’, “Milhouse Van Houten”);    if (id == 2) return std::make_tuple(1.7, ‘D’, “Ralph Wiggum”);    throw std::invalid_argument(“id”);    auto student0 = get_student(0);              << "GPA: " << std::get(student0) << ", "              << "grade: " << std::get(student0) << ", "              << "name: " << std::get(student0) << '\n';

What if You by mistake switch ‘GPA’ and ‘grade’ inside your function? Nothing will happen to inform You that something went wrong. Information will still be passed to std::make_tuple, and auto-converted to std::cout. Except the output will be totally wrong… Is there some way to avoid this situation? A lot of you now are thinking about making custom struct with named fields, than and passing typed result as this struct. This is good solution and there are a lot of cases when this will be actually better, but … this is not so simple.

But what if you know that this temporary structure is rather limited in size and will be used just ‘here’ to pass results (and so creating of additional class to pass result will be overhead) – there is nice solution from python – named tuple.

C++14 is powerful enough to make our own named tuple. The only problem is that final syntax could be different depending on our own choice. I have chosen the following:

auto student0 = make_named_tuple(param(“GPA”) = 3.8, param(“grade”) = ‘A’, param(“name”) = “Lisa Simpson”);auto gpa = student0[param(“GPA”)];auto grade = student0[param(“grade”)];auto student0 = make_named_tuple(param(“GPA”) = 3.8, param(“grade”) = ‘A’, param(“name”) = “Lisa Simpson”);auto gpa = student0[param(“GPA”)];auto grade = student0[param(“grade”)];

Creation: Instead of make_tuple() here we have make_named_tuple() and param() is creating named parameter which is set in place.

Access: To access data I use square bracers and same named parameter inside.

There are a lot of other ways to declare this – you are free to use your own syntax. It will be better then std::get anyway.

IMPLEMENTATION of tagged tuple

The main trick here is compile time string hash. It does not matter which specific implementation You choose for it – I just grabbed the one from Manu’s gist:

namespace foonathan { namespace string_id { namespace detail { using hash_type = std::uint64_t; constexpr hash_type fnv_basis = 14695981039346656037ull; constexpr hash_type fnv_prime = 109951162821ull; // FNV-1a 64 bit hash constexpr hash_type sid_hash(const char *str, hash_type hash = fnv_basis) noexcept { return *str ? sid_hash(str + 1, (hash ^ *str) * fnv_prime) : hash; } } }} // foonathan::string_id::detail            using hash_type = std::uint64_t;            constexpr hash_type fnv_basis = 14695981039346656037ull;            constexpr hash_type fnv_prime = 109951162821ull;            constexpr hash_type sid_hash(const char *str, hash_type hash = fnv_basis) noexcept                return *str ? sid_hash(str + 1, (hash ^ *str) * fnv_prime) : hash;} // foonathan::string_id::detail

Next is simple class for named param:

/// Named parameter (could be empty!)template struct named_param { using hash = Hash; ///< key std::tuple value; ///< param's data itself named_param(Ts&&… ts) : value(std::forward(ts)…){ }; ///< constructor template named_param operator=(P&& p){ return named_param(std::forward

(p)); }; }; template using make_named_param = named_param;/// Named parameter (could be empty!)template     using hash = Hash;                                                  ///< key    std::tuple value;                                            ///< param's data itself    named_param(Ts&&… ts) : value(std::forward(ts)…){ };        ///< constructor    named_param operator=(P&& p){ return named_param(std::forward

(p)); };using make_named_param = named_param;

Nothing special as You could see – i just store optional value inside inner tuple. So parameter could be empty and be used as static search key or it can contain some value and be passed to tuple construction function.

Now main tuple struct. To make it work with Visual Studio’s compiler I came up with this:

/// Named tuple is just tuple of named params template struct named_tuple : public std::tuple { template named_tuple(Args&&… args) : std::tuple(std::forward(args)…) {} static const std::size_t error = -1; template constexpr typename std::enable_if::type static get_element_index() { return error; } template constexpr typename std::enable_if<I ::type static get_element_index() { using elementType = typename std::tuple_element<I, std::tuple>::type; return (std::is_same::value) ? I : get_element_index(); } template const auto& get() const { constexpr std::size_t index = get_element_index(); static_assert((index != error), “Wrong named tuple key”); auto& param = (std::get(static_cast<const std::tuple&>(*this))); return std::get( param.value ); } template const auto& operator[](NP&& param) { return get(); } };  /// Named tuple is just tuple of named params        struct named_tuple : public std::tuple            named_tuple(Args&&… args) : std::tuple(std::forward(args)…) {}            static const std::size_t error = -1;            template            constexpr typename std::enable_if::type            template            constexpr typename std::enable_if<I ::type                using elementType = typename std::tuple_element<I, std::tuple>::type;                return (std::is_same::value) ? I : get_element_index();                constexpr std::size_t index = get_element_index();                static_assert((index != error), “Wrong named tuple key”);                auto& param = (std::get(static_cast<const std::tuple&>(*this)));                return std::get( param.value );            const auto& operator[](NP&& param)                return get();

And finally make_named_tuple() and param():

template auto make_named_tuple(Args&&… args){ return named_tuple(std::forward(args)…);} #define param(x) make_named_param< std::integral_constant >{}auto make_named_tuple(Args&&… args)    return named_tuple(std::forward(args)…);#define param(x) make_named_param< std::integral_constant >{}

Yes, that’s all!

CONCLUSION

So we now have rather compact way to create named tuples in C++. This has to improve readability and simplicity of any code dealing with std::tuples and std::pairs.

And what is even more important – this scheme is more error proof. When you change places of arguments in tuple – nothing happens – everything will still work. If you make mistake in parameter name – you will get compile error right away. If during project lifetime You decide to refactor your tuple structure you will get all places where you need to change access functions, because until you do this compiler will not produce any executable. Sweet.

Source code: gist

View the original article here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s