Есть ли карта std :: tuple - PullRequest
       50

Есть ли карта std :: tuple

0 голосов
/ 01 апреля 2020

Существует ли std::tuple там, где это вместо карты вместо целочисленного ключа? Где ключ, очевидно, constexpr.

Если нет, как можно достичь реализации этого?

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*/>(); }
};

Ответы [ 2 ]

1 голос
/ 02 апреля 2020

Может быть, есть лучшие способы, но я представляю что-то следующим образом

#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;
 }
0 голосов
/ 02 апреля 2020

Извините за то, что я оставил вас в покое, я был прерван после первоначального чтения, поэтому я мог посещать церковное богослужение в режиме онлайн (поскольку мы в значительной степени ограничены нашими домами и группами из 3 человек или меньше, если не дома).

Итак, на самом деле есть много способов сделать это, поэтому мы исследуем пару, а затем дадим советы о том, как делать другие ...

FWIW, я действительно рекомендую использовать библиотеку метапрограммирования для этого вещи. Boost.Hana, вероятно, самый простой в использовании и наиболее гибкий. Kvasir, пожалуй, самый быстрый, но немного менее гибкий и, хм ... не такой, как обычно.

Это здесь, в основном, чтобы показать некоторые базовые c инструменты в надежде, что вы сами сможете разработать что-то еще, что наилучшим образом соответствует вашим требованиям - если вы решите не использовать библиотеку метапрограммирования.

Я скажу, что как только вы импортируете библиотеку метапрограммирования, она почти всегда окупается, потому что она используется снова и снова. Если это было какое-то время, вы можете дать им еще один шанс - сейчас они быстрее компилируются - просто не используйте Boost.MPL, кроме как для изучения. Это затрудняется из-за необходимости обратной совместимости.

Во-первых, полностью ручной пример, который немного настраивается, но достаточно гибок.

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

Вы также можете просто передайте список типов тегов другому классу, который представляет сопоставленный кортеж ...

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

Вы можете расширить последний, чтобы отправить список пар, который будет сочетанием двух, но изолируйте карту от внутренней части класса. Это дает вам возможность отображать несколько типов в один и тот же индекс, если вы того пожелаете.

Вы также можете использовать строки, либо вычисляя га sh - хотя я бы по крайней мере использовал MD5 га sh чтобы уменьшить вероятность столкновения - не так уж сложно сделать это с помощью функций constexpr.

Или вы можете сделать что-то вроде этого ...

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

Это работает только для clang и g cc, хотя я уверен, что другие компиляторы имеют похожие макросы.

Поскольку tname теперь является встроенной переменной constexpr, вы можете использовать ссылки на них в качестве параметров шаблона, что дает вам возможность для создания шаблонов, используя «имя» типа.

Или вы можете просто засунуть string_view в таблицу constexpr. Вы можете выполнить простой линейный поиск или отсортировать их и выполнить бинарный поиск, или превратить таблицу в таблицу ha sh. Как и в обычном программировании, у каждого есть свое место.

Затем вы можете сохранить строку и индекс в таблице constexpr и выполнить поиск, чтобы найти их.

gcc / clang также поддерживает необработанные пользовательские определения строковые литералы, которые могут принимать строку и создавать уникальный тип, используя отдельные символы в качестве аргументов для набора c не типовых параметров типа char.


EDIT

Еще одна опция, о которой я подумал во время перерыва ... чтобы продемонстрировать что-то немного другое, но, возможно, полезное ...

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{}])>);

Вы можете сделать эту бесплатную функцию работать с любым подобным кортежу объектом, но это потребовало бы немного больше работы ...

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