1

I'm trying to implement some kind of map (a tuple of pair) which use compile time string as key (first element of the pair). So I wanted to use this answer but there is a problem with my code : the string is inside a pair.

#include <type_traits>
#include <tuple>

namespace meta {

    template < typename T >
    struct CType { using type = T; };


  namespace detail {
    template <typename T>
    struct typeid_t {
      using type = typename std::remove_cv<
        typename std::remove_reference<T>::type
      >::type;
    };
  }

  template <typename T>
  constexpr decltype(auto) typeid_(T&&) {

    return CType<typename detail::typeid_t<T>::type>{};
  }
}


  struct  HashConstString {

    using value_type = uint32_t;

    static constexpr uint32_t hash(const char* str) {
        return str[0];
    }
  };


template < typename T_Hash,
           typename... T_Pairs >

  class  UniversalMap {

        template < typename T_Pair >
        using U_Pair = decltype(std::make_pair(
          std::integral_constant<typename T_Hash::value_type, T_Hash::hash(std::get<0>(T_Pair{}))>{},
          typename decltype(meta::typeid_(std::get<1>(T_Pair{})))::type {}
        ));

      using U_Map = decltype(std::make_tuple(
        U_Pair<T_Pairs>{}...
      ));

      private:
        U_Map        m_map;
  };

template < typename T_Hash,
           typename... T_Pairs >
    constexpr decltype(auto)  make_UniversalMap(T_Hash hash, T_Pairs... pairs) {

    (void)hash;
    ((void)pairs,...);
    return UniversalMap<T_Hash, T_Pairs...>();
}

int main() {

    constexpr auto hashValue = HashConstString::hash("Test");
    constexpr auto map = make_UniversalMap(HashConstString{},
      std::make_pair("Test", meta::CType<int>{})
    );
}

Wandbox

So I don't know how to hash correctly the string when it's already inside the pair. Because std::get give me back a reference and it seems it's the reason why I have a dereferenced null pointer error.

Is there some "tricks" to get this work without having to compute the hash before creating the pair?

Mathieu Van Nevel
  • 1,428
  • 1
  • 10
  • 26

1 Answers1

1

The problem is not with std::get but with the fact that you create a tuple of const char*. "Test" decays to const char* when passed as argument to make_pair. Unfortunately explicitly specifying the pair template parameters (e.g. std::pair<const char[5], int>) does not work because you can't create a std container of type array.

The rather awkward solution is to use std::array:

struct HashConstString
{
  using value_type = uint32_t;
  static constexpr uint32_t hash(const char *str) { return str[0]; }

  // add this overload
  template <std::size_t N>
  static constexpr uint32_t hash(std::array<char, N> str) { return str[0]; }
};

and then call like this:

constexpr auto map = make_UniversalMap(HashConstString{},
                         std::make_pair(std::array<char, 5>{"Test"}, int{}));

To avoid specifying the size for std::array you can create a helper function:

template <std::size_t N> constexpr auto make_strarray(const char(&str)[N])
{
    // unfortunately std::array<char, N>{str} does not work :(
    std::array<char, N> arr{};

    for (std::size_t i = 0; i < N; ++i)
        arr[i] = str[i];

    return arr;
}

Or since in C++20 it looks like std::copy will be made constexpr:

template <std::size_t N> constexpr auto make_strarray(const char(&str)[N])
{
    std::array<char, N> arr{};
    std::copy(str, str + N, arr.begin());
    return arr;
}
bolov
  • 72,283
  • 15
  • 145
  • 224