简体   繁体   中英

Is there a std::tuple map

Does an std::tuple exist where it is instead a map instead of an integral key? Where the key is obviously constexpr.

If not, how can one achieve an implementation of this?

template< typename key, typename... pack >
class tuple_map
{
    std::tuple< pack... > tuple;
public:
    template< key k >
    std::tuple_element</*magic to translate k to an integer*/, tuple<pack...>> get()
    { return tuple.get</*magic k*/>(); }
};

Maybe there are better ways, but I imagine something as follows

#include <iostream>
#include <tuple>

enum Key { keyA, keyB, keyC };

template <Key ... keys>
struct key_map
 {
   static constexpr std::size_t getIndex (Key val)
    {
      std::size_t ret {};

      std::size_t ind {};

      ((ret = -1), ..., ((keys == val ? ret = ind : ind), ++ind));

      if ( -1 == ret )
         throw std::runtime_error("no valid key");

      return ret;
    }
 };

template<typename... pack>
struct tuple_map
 {
   std::tuple< pack... > tuple;

   using km = key_map<keyA, keyB, keyC>;

   template <Key K>
   auto const & get () const
    { return std::get<km::getIndex(K)>(tuple); }

   template <Key K>
   auto & get ()
    { return std::get<km::getIndex(K)>(tuple); }
 };

int main ()
 {
   tuple_map<int, std::string, long>  tm{{0, "one", 2l}};

   std::cout << tm.get<keyA>() << std::endl;
   std::cout << tm.get<keyB>() << std::endl;
   std::cout << tm.get<keyC>() << std::endl;
 }

Sorry for leaving you hanging, I was interrupted after initially reading so I could attend a devotional church service online (as we are pretty much confined to our homes and groups of 3 or less unless at home).

So, there are actually a lot of ways to do this, so we will explore a couple and then give pointers on how to do others...

FWIW, I really do recommend using a metaprogramming library for this stuff. Boost.Hana is probably the easiest to use and most flexible. Kvasir is probably the fastest, but a bit less flexible and, um..., not as conventional.

This is here mostly to show some basic tools in hopes that you can develop something else yourself that best fits your requirements - if you choose to not use a metaprogramming library.

I'll say that once you import a metaprogramming library, it almost always pays for itself because it gets used over and over. If it's been a while, you may want to give them another chance - they are way faster to compile now days - just don't use Boost.MPL except as something to learn by. It is hampered by having to be so backwards compatible.

First, a totally by-hand example, which is a bit of setup, but is quite flexible.

// Primary template that provides the tuple index for a tuple/key pair
template <typename Tuple, typename Key>
struct TupleIndex : std::integral_constant<int, -1> { };

// Some key tag types
struct Foo { };
struct Blarg { };
struct Blip { };
struct Baz { };

// Explicit instantiations to map types and indices
// To actually use this, you will want a metafunction to create these
// and validate that the index is in the proper range.
template <> struct TupleIndex<std::tuple<int, double, char, float>, Foo>
: std::integral_constant<int, 0> { };
template <> struct TupleIndex<std::tuple<int, double, char, float>, Blarg>
: std::integral_constant<int, 1> { };
template <> struct TupleIndex<std::tuple<int, double, char, float>, Blip>
: std::integral_constant<int, 2> { };
template <> struct TupleIndex<std::tuple<int, double, char, float>, Baz>
: std::integral_constant<int, 3> { };

namespace detail {
template <typename T> struct a_tuple { };
template <typename ... Ts> struct a_tuple<std::tuple<Ts...>>
{
    using type = std::tuple<Ts...>;
};
}
template <typename Key, typename T,
          typename Tuple = typename detail::a_tuple<std::decay_t<T>>::type>
decltype(auto)
tuple_get(T && tuple)
{
    if constexpr (TupleIndex<Tuple, Key>::value >= 0) {
        return std::get<TupleIndex<Tuple, Key>::value>(std::forward<T>(tuple));
    } else {
        return std::get<Key>(std::forward<T>(tuple));
    }
}

// Some simple tests...
using Tuple = std::tuple<int, double, char, float>;
static_assert(std::is_same_v<int&&,
        decltype(tuple_get<int>(std::declval<Tuple>>()))>);
static_assert(std::is_same_v<double&&,
        decltype(tuple_get<double>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<char&&,
        decltype(tuple_get<char>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<float&&,
        decltype(tuple_get<float>(std::declval<Tuple>()))>);

static_assert(std::is_same_v<int&&,
        decltype(tuple_get<Foo>(std::declval< Tuple >()))>);
static_assert(std::is_same_v<double&&,
        decltype(tuple_get<Blarg>(std::declval< Tuple >()))>);
static_assert(std::is_same_v<char&&,
        decltype(tuple_get<Blip>(std::declval< Tuple >()))>);
static_assert(std::is_same_v<float&&,
        decltype(tuple_get<Baz>(std::declval< Tuple >()))>);

You could also just pass a list of tag types to another class that represents a mapped tuple...

// Some key tag types
struct Foo { };
struct Blarg { };
struct Blip { };
struct Baz { };

// A simple pack of types
template <typename ... Ts> struct Pack { };

// Some basic stuff to find the index of a type in a pack
namespace detail {
template <typename T, std::size_t I>
struct TypeAndIndex
{
    using type = T;
    using index = std::integral_constant<std::size_t, I>;
};
template <typename T, std::size_t Ndx>
static typename TypeAndIndex<T, Ndx>::index
check_type(TypeAndIndex<T, Ndx> *);
template <typename T>
static std::integral_constant<std::size_t, std::size_t(-1)>
check_type(void const *);
template <typename... Ts>
struct IndexOfType
{
    template <typename Seq>
    struct Impl;
    template <std::size_t... Is>
    struct Impl<std::index_sequence<Is...>> : TypeAndIndex<Ts, Is>...
    {};
    using Seq = std::make_index_sequence<sizeof...(Ts)>;
    template <typename T>
    using apply = decltype(check_type<T>(static_cast<Impl<Seq> *>(nullptr)));
};
}
template <typename TargetT, typename... Ts>
struct IndexOf : detail::IndexOfType<Ts...>::template apply<TargetT>
{ };
template <typename TargetT, typename... Ts>
struct IndexOf<TargetT, Pack<Ts...>> : IndexOf<TargetT, Ts...>
{ };

// A mapped-tuple type, that takes a Pack of keys and a tuple types
template <typename PackT, typename ... Ts>
struct MappedTuple;
template <typename ... KeyTs, typename ... Ts>
struct MappedTuple<Pack<KeyTs...>, Ts...> : std::tuple<Ts...>
{
    static_assert(sizeof...(KeyTs) == sizeof...(Ts));
    using Keys = Pack<KeyTs...>;
    using Tuple = std::tuple<Ts...>;
};
template <typename T>
struct IsMappedTuple : std::false_type { };
template <typename ... KeyTs, typename ... Ts>
struct IsMappedTuple<MappedTuple<Pack<KeyTs...>, Ts...>> : std::true_type { };

// Get the type, by key
template <typename Key, typename T,
          std::enable_if_t<IsMappedTuple<std::decay_t<T>>::value, bool> = true>
decltype(auto)
get(T && tuple)
{
    using Keys = typename std::decay_t<T>::Keys;
    if constexpr (IndexOf<Key, Keys>::value != std::size_t(-1)) {
        return std::get<IndexOf<Key, Keys>::value>(std::forward<T>(tuple));
    } else {
        return std::get<Key>(std::forward<T>(tuple));
    }
}

// The same set of tests
using Tuple = MappedTuple<Pack<Foo,Blarg,Blip,Baz>,
        int, double, char, float>;

static_assert(std::is_same_v<int&&,
        decltype(get<int>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<double&&,
        decltype(get<double>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<char&&,
        decltype(get<char>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<float&&,
        decltype(get<float>(std::declval<Tuple>()))>);

static_assert(std::is_same_v<int&&,
        decltype(get<Foo>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<double&&,
        decltype(get<Blarg>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<char&&,
        decltype(get<Blip>(std::declval<Tuple>()))>);
static_assert(std::is_same_v<float&&,
        decltype(get<Baz>(std::declval<Tuple>()))>);

You could expand the last one to send in a list of pairs, which would be a mix of the two, but isolate the map to the interior of the class. This gives you the option of mapping multiple types to the same index if you so desire.

You could also use strings, either by computing a hash - though I would at least use a MD5 hash to reduce the likelihood of a collision - not too hard to get that done with constexpr functions.

Or, you could do something like this...

template <typename T>
constexpr auto fname()
{
    return std::string_view(__PRETTY_FUNCTION__);
}
template <typename T>
inline constexpr std::string_view tname = fname<T>();

This only works for clang and gcc, though I'm sure other compilers have similar macros.

Since tname is now an inline constexpr variable, you can use references to them as template parameters, thus giving you a way to create templates using the "name" of a type.

Or you can just shove the string_view into a constexpr table. You can do a simple linear search, or sort them and do a binary search, or turn the table into a hash table. As in normal programming, each has its place.

You could then store the string and index in a constexpr table and do a lookup to find them.

gcc/clang also support raw user defined string literals, which can take a string and create a unique type using the individual characters as arguments to a variadic set of char non-type parameters.


EDIT

Just one more option I thought about while taking a break... to demonstrate something a bit different, but possibly useful...

template <std::size_t Ndx>
struct TokenMapKey
: std::integral_constant<std::size_t, Ndx>
{
};

template <typename ... Ts>
struct MappedTuple : std::tuple<Ts...>
{
    template <std::size_t N>
    decltype(auto) operator[](TokenMapKey<N> key) const & noexcept
    { return std::get<N>(*this); }

    template <std::size_t N>
    decltype(auto) operator[](TokenMapKey<N> key) & noexcept
    { return std::get<N>(*this); }

    template <std::size_t N>
    decltype(auto) operator[](TokenMapKey<N> key) const && noexcept
    { return std::get<N>(std::move(*this)); }

    template <std::size_t N>
    decltype(auto) operator[](TokenMapKey<N> key) && noexcept
    { return std::get<N>(std::move(*this)); }
};

struct Foo : TokenMapKey<0> { };
struct Blarg : TokenMapKey<1> { };
struct Blip : TokenMapKey<2> { };
struct Baz : TokenMapKey<3> { };

using TT = MappedTuple<int, double, char, float>;
static_assert(std::is_same_v<int&&,
        decltype(std::declval<TT>()[Foo{}])>);
static_assert(std::is_same_v<double&&,
        decltype(std::declval<TT>()[Blarg{}])>);
static_assert(std::is_same_v<char&&,
        decltype(std::declval<TT>()[Blip{}])>);
static_assert(std::is_same_v<float&&,
        decltype(std::declval<TT>()[Baz{}])>);

You could make this a free function to work with any tuple-like object, but it would require a bit more work...

template <std::size_t Ndx>
struct TokenMapKey
: std::integral_constant<std::size_t, Ndx>
{
};

struct Foo : TokenMapKey<0> { };
struct Blarg : TokenMapKey<1> { };
struct Blip : TokenMapKey<2> { };
struct Baz : TokenMapKey<3> { };

namespace detail {
using std::get;
template <std::size_t N, typename T>
constexpr auto
get(std::integral_constant<std::size_t, N>, T && t)
-> decltype(get<N>(std::forward<T>(t)))
{
    return get<N>(std::forward<T>(t));
}
}

// Get using an integral constant parameter...
template <std::size_t N, typename T>
constexpr auto
get(std::integral_constant<std::size_t, N> k, T && t)
-> decltype(detail::get(k, std::forward<T>(t)))
{
    return detail::get(k, std::forward<T>(t));
}

// Note the name here - getk, because the one in the std namespace
// gets selected by ADL.
template <typename Key, typename T>
constexpr auto
getk(T && t)
-> decltype(get(std::declval<typename Key::type>(), std::forward<T>(t)))
{
    return get(typename Key::type {}, std::forward<T>(t));
}

using Tpl = std::tuple<int, double, char, float>;
static_assert(std::is_same_v<int&&,
        decltype(getk<Foo>(std::declval<Tpl>()))>);
static_assert(std::is_same_v<double&&,
        decltype(getk<Blarg>(std::declval<Tpl>()))>);
static_assert(std::is_same_v<char&&,
        decltype(getk<Blip>(std::declval<Tpl>()))>);
static_assert(std::is_same_v<float&&,
        decltype(getk<Baz>(std::declval<Tpl>()))>);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM