Skip to content

Instantly share code, notes, and snippets.

@tarqd
Last active November 11, 2021 05:20
Show Gist options
  • Save tarqd/dd91285d50197d6345f9 to your computer and use it in GitHub Desktop.
Save tarqd/dd91285d50197d6345f9 to your computer and use it in GitHub Desktop.
Tagged Tuples

The MIT License (MIT)

Copyright (c) 2015 Christopher Tarquini

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include <iostream>
#include <tuple>
#include "tagged_tuple.hpp"
int main(int argc, char* argv[]) {
// defines binding between types (names/tags)
// easier to maintain than just going with tuple<tag, type, tag, type, etc...>
// no chance of you accidently removing a only a tag and setting the whole list off balance
// also they are re-usable so you can always bind a certain type to a name
using tagged_tuple::type_pair;
using tagged_tuple::tagged_tuple;
// bring in our get function for ADL
using tagged_tuple::get;
// can co-exist with std::get
using std::get;
// define a tagged tuple
// instead of using type_pair<key_t, value_t> you can define a `name_tag` type on your classes and tagged_tuple will use that type as the key
// for example struct user; class User { using name_tag = user; }; tagged_tuple<User> foo; auto val = get<user>(foo);
using user_t = tagged_tuple<type_pair<struct name, std::string>, type_pair<struct age, int>>;
// it's initialized the same way as a tuple created with the value types of the type pairs (so tuple<string, int> in this case)
user_t user { "chris", 21 };
std::cout << "Name: " << get<name>(user) << std::endl;
std::cout << "Age: " << get<age>(user) << std::endl;
++get<age>(user);
std::cout << "Age: " << get<age>(user) << std::endl;
// you can still access properties via numeric indexes as if the class was defined as tuple<string, int>
std::cout << "user[0] = " << get<0>(user) << std::endl;
// tagged_tuple is derives from tuple<value_types<TagPairs>...> (in this example tuple<string, int>)
// so it's implicitly convertible
std::tuple<string, int> regular_tuple { user };
user_t another_user { regular_tuple };
// if you don't like this you just need to make tagged_tuple privately derive from std::tuple instead of publically
// I don't think splicing is an issue because it adds no data members. Just keeps track of the TagType -> Index mapping
// The mapping is done statically so there's no allocations or memory overhead. Should be just as fast as using a normal tuple with proper inlining
}
namespace tagged_tuple {
// should probably move all these helper templates into a detail namespace
template <class T>
struct has_name_tag_helper {
template<class U> static std::true_type check(typename U::name_tag*);
template<class U> static std::false_type check(...);
static constexpr bool value = decltype(check<T>(nullptr))::value;
};
// detects if a type has a name_tag type defined
template <class T>
struct has_name_tag : std::integral_constant<bool, has_name_tag_helper<T>::value> {};
// simple class to hold two types
// works with incomplete types, important for our use case
template <class First, class Second>
struct type_pair {
using first = First;
using second = Second;
};
template <class T>
struct is_type_pair_helper {
template <class T1, class T2> static std::true_type check(type_pair<T1, T2>* ptr);
template <class T1> static std::false_type check(T1*);
static constexpr bool value = decltype(check((T*)(0)))::value;
};
// detects if T is a type_pair
template <class T>
using is_type_pair = std::integral_constant<bool, is_type_pair_helper<T>::value>;
template <class T, bool IsTag = is_type_pair<typename T::name_tag>::value>
struct name_tag_traits_helper;
template <class T>
struct name_tag_traits_helper<T, true> {
using type = typename T::name_tag;
};
template <class T>
struct name_tag_traits_helper<T, false> {
using type = type_pair<typename T::name_tag, T>;
};
template <class T, bool IsTypePair = is_type_pair<T>::value, bool HasNameTag = has_name_tag<T>::value>
struct base_name_tag_traits;
template <class T>
struct base_name_tag_traits<T, true, false> {
using type = T;
};
template <class T>
struct base_name_tag_traits<T, false, true> {
using type = typename std::conditional<is_type_pair<T>::value, typename T::name_tag, type_pair<typename T::name_tag, T>>::type;
};
// gets info about a classes name tag (key -> value type mapping) from the class
// if it's a type info it just uses that
// if the class has a name_tag type it will use it if it's a type_pair, otherwise it'll be the same as type_pair<typename T::name_tag, T>
template <class T>
struct name_tag_traits : base_name_tag_traits<T>::type {
// aliases for our specific use case
using tag_type = typename base_name_tag_traits<T>::type::first;
using value_type = typename base_name_tag_traits<T>::type::second;
using pair_type = typename base_name_tag_traits<T>::type;
};
template <class T, class U, class... Types>
constexpr std::size_t do_type_count(std::size_t count = 0) {
return do_type_count<T, Types...>(count + std::is_same<T, U>::value ? 1 : 0);
};
template <class T>
constexpr std::size_t do_type_count(std::size_t count = 0) { return count; };
// counts the number of times T appears in parameter pack, eventually we should use this to make sure
// that the name_tag_traits::tag_type only appears once in a tagged tuple list
template <class T, class... Types>
constexpr std::size_t type_count() {
return do_type_count<T, Types...>();
};
// helper alias, turns a list of TypePairs supplied to tagged_tuple to a list of key/tag/name types
template <class T>
using name_tag_t = typename name_tag_traits<T>::tag_type;
// same as above but returns a list of value types
template <class T>
using name_tag_value_t = typename name_tag_traits<T>::value_type;
template <class Needle>
constexpr size_t index_of_impl(size_t index, size_t end) {
return end;
};
template <class Needle, class T, class... Haystack>
constexpr size_t index_of_impl(size_t index, size_t end) {
return std::is_same<Needle, T>::value ? index : index_of_impl<Needle, Haystack...>(index + 1, end);
};
// find the index of T in a type list, returns sizeof...(Haystack) + 1 on failure (think std::end())
template <class Needle, class... Haystack>
static constexpr size_t index_of() {
return index_of_impl<Needle, Haystack...>(0, sizeof...(Haystack) + 1);
};
// and here's our little wrapper class that enables tagged tuples
template <class... TypePairs>
class tagged_tuple : public std::tuple<name_tag_value_t<TypePairs>...> {
public:
// not really needed for now but if we switch to private inheritance it'll come in handy
using tag_type = std::tuple<name_tag_t<TypePairs>...>;
using value_type = std::tuple<name_tag_value_t<TypePairs>...>;
using value_type::value_type;
using value_type::swap;
using value_type::operator=;
};
// our special get functions
template <class Name, class... TypePairs>
auto get(tagged_tuple<TypePairs...>& tuple) ->
typename std::tuple_element<index_of<Name, name_tag_t<TypePairs>...>(),
typename tagged_tuple<TypePairs...>::value_type>::type&
{
return std::get<index_of<Name, name_tag_t<TypePairs>...>()>(tuple);
};
template <class Name, class... TypePairs>
auto get(const tagged_tuple<TypePairs...>& tuple) ->
const typename std::tuple_element<index_of<Name, name_tag_t<TypePairs>...>(),
typename tagged_tuple<TypePairs...>::value_type>::type&
{
return std::get<index_of<Name, name_tag_t<TypePairs>...>()>(tuple);
};
template <class Name, class... TypePairs>
auto get(tagged_tuple<TypePairs...>&& tuple) ->
typename std::tuple_element<index_of<Name, name_tag_t<TypePairs>...>(),
typename tagged_tuple<TypePairs...>::value_type>::type&&
{
return std::get<index_of<Name, name_tag_t<TypePairs>...>()>(tuple);
};
}
@PR0BLEMCH1LD
Copy link

line 130 should be return std::get<index_of<Name, name_tag_t<TypePairs>...>()>(std::move(tuple));

@tarqd
Copy link
Author

tarqd commented Nov 9, 2021

I'll have to take your word for it because I haven't the foggiest idea how this works anymore

@PR0BLEMCH1LD
Copy link

I recently got into learning template metaprogramming and found your post on https://stackoverflow.com/questions/13065166/c11-tagged-tuple. It would be amazing if you implemented a check for name uniqueness at compile time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment