简体   繁体   English

是否有 std::tuple 映射

[英]Is there a std::tuple map

Does an std::tuple exist where it is instead a map instead of an integral key?是否存在std::tuple而不是映射而不是整数键? Where the key is obviously constexpr.其中关键显然是 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).很抱歉让你悬而未决,我在最初阅读后被打断了,所以我可以参加在线灵修教堂服务(因为除非在家,否则我们几乎仅限于我们的家和 3 人或更少的团体)。

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. FWIW,我真的建议为这些东西使用元编程库。 Boost.Hana is probably the easiest to use and most flexible. Boost.Hana 可能是最容易使用和最灵活的。 Kvasir is probably the fastest, but a bit less flexible and, um..., not as conventional. Kvasir 可能是最快的,但灵活性稍差,嗯......,不像传统的那样。

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.如果已经有一段时间了,您可能想再给他们一次机会 - 现在它们的编译速度要快得多 - 只是不要使用 Boost.MPL 除非作为学习的东西。 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.您也可以使用字符串,或者通过计算散列 - 尽管我至少会使用 MD5 散列来减少冲突的可能性 - 使用 constexpr 函数来完成它并不难。

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.这仅适用于 clang 和 gcc,但我确信其他编译器也有类似的宏。

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.由于tname现在是一个内联 constexpr 变量,您可以使用对它们的引用作为模板参数,从而为您提供一种使用类型的“名称”创建模板的方法。

Or you can just shove the string_view into a constexpr table.或者您可以将 string_view 推入 constexpr 表中。 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.然后,您可以将字符串和索引存储在 constexpr 表中并进行查找以找到它们。

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. gcc/clang 还支持原始用户定义的字符串文字,它可以接受一个字符串并使用单个字符作为参数到一组 char 非类型参数的可变参数来创建一个唯一类型。


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>()))>);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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