0

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*/>(); }
};
nowi
  • 437
  • 4
  • 13
  • Are you expecting a compile-time error or a run-time error? – Brian Bi Apr 01 '20 at 20:29
  • no `std::any` and no `std::variant` ? – max66 Apr 01 '20 at 20:29
  • @Brain compile-time – nowi Apr 01 '20 at 20:30
  • @max66 unfortunately not. – nowi Apr 01 '20 at 20:30
  • maybe `std::tuple` ? I mean: `auto m = std::make_tuple( std::string{"hello"}, 8 );` and `std::get<0>(m) += "world"; std::get<1>(m) += 4;` – max66 Apr 01 '20 at 20:33
  • @max66 Yeah that's probably what it's going to boil down to – nowi Apr 01 '20 at 20:34
  • Or maybe `auto m = std::make_tuple( new std::string{"hello"}, new int{8} );` and `*std::get<0>(m) += "world"; *std::get<1>(m) += 4;` if you want maintain the pointers part. – max66 Apr 01 '20 at 20:35
  • @max66 That would achieve the homogeneous part of the question from before so that sounds like it will work out. Is there some sort of "tuple map" which allows custom keys? Also, how are tuples stored in memory? – nowi Apr 01 '20 at 20:37
  • Okay, maybe it'd be better for me to change this question to "tuple map" instead of compile-time type erasure since that question is no longer relevant – nowi Apr 01 '20 at 20:42
  • But what is `key`? An integral type? An enum? – max66 Apr 01 '20 at 21:01
  • @max66 A string if possible would be best but really anything that is explicit when looking at the code is preferable. An enum would be worst case scenario, I suppose. – nowi Apr 01 '20 at 21:02
  • `std::get()` does not work for you? Do you want any arbitrary type to map to some tuple element or a string or what? – Jody Hagins Apr 01 '20 at 22:06
  • @JodyHagins there are duplicate types – nowi Apr 01 '20 at 22:07

2 Answers2

1

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;
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Anyway this can be done with string hashing so that strings can be used compile time? https://stackoverflow.com/questions/2111667/compile-time-string-hashing – nowi Apr 01 '20 at 21:30
  • @nowi - well... the string ashing that you link produce `constexpr` `std::uint32_t`s ... a `std::uint32_t` can be a template value parameter... so... yes, I suppose it's possible. – max66 Apr 01 '20 at 22:14
  • boost::hana has compile time string type hana::string. These strings are iterable, comparable, searchable, and even hashable. All at compile time. ;) – facetus Apr 02 '20 at 00:23
-1

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>()))>);
Jody Hagins
  • 27,943
  • 6
  • 58
  • 87