简体   繁体   中英

std::map::size_type for a std::map whose value_type is its own size_type

I have a std::map<std::pair<std::string, std::string>, float> that is taking up too much memory, and in order to use less memory, I've decided to map the unique strings to integers (eg, std::map<std::string, int> , where each new unique string is mapped to the current size() of the map), and use those integer value as pairwise keys to the map, (eg, std::map<std::pair<int, int>, float> ).

Instead of int , I want to use std::map::size_type :

using map_index = std::map::size_type;
std::pair<map_index, map_index> key;

Of course, this doesn't compile because I need to supply the argument list for the map:

vector.cc:14:19: error: invalid use of template-name `std::map' without an argument list
 using map_index = std::map::size_type;

And this (in theory) is what I'm trying to achieve:

using map_index = std::map<std::string, map_index>::size_type;

which gives the following (expected) compiler error:

vector.cc:15:41: error: `map_index' was not declared in this scope
 using map_index = std::map<std::string, map_index>::size_type;

What is the proper way to get the compiler to infer the correct value_type for a std::map whose value_type is its own size_type ?

size_t should be good enough for such case.

But if you insist, you can do like this:

#include <type_traits>
#include <map>

template <class Key, class Value = size_t, size_t depth = 0, class = void>
struct GetSizeType {
    using type = typename GetSizeType<Key, typename std::map<Key, Value>::size_type, depth + 1>::type;
};

template <class Key, class Value, size_t depth>
struct GetSizeType<Key, Value, depth, std::enable_if_t<std::is_same_v<Value, typename std::map<Key, Value>::size_type>>> {
    using type = typename std::map<Key, Value>::size_type;
};

template <class Key, class Value>
struct GetSizeType<Key, Value, 100, void> {};

int main() {
    using X = GetSizeType<int>::type;

    return 0;
}

It will run recursively on GetSizeType , the recursive call will stop upon

  • reaching the recursive call depth limitation (there will be no member type in this case), or
  • finding a specialization of std::map of which the mapped_type and size_type is identical (the member type aliases size_type ).

Disclaimer: this solution is pretty dumb. We're just going to solve the equation by repeatedly (typically once) trying to instantiate std::map until we find one that has the requested key and its own size_type as value.

template <class T>
struct identity {
    using type = T;
};

template <class K, class V = char>
struct auto_map {
    using map_type = std::map<K, V>;
    using type = typename std::conditional_t<
        std::is_same_v<
            typename map_type::mapped_type,
            typename map_type::size_type
        >,
        identity<map_type>,
        auto_map<K, typename map_type::size_type>
    >::type;
};

template <class K>
using auto_map_t = typename auto_map<K>::type;

If the metafunction can't find such a map, it will either error out because type ends up defined to itself, or break the recursion limit.

Use std::size_t . The unsigned integer std::map::size_type won't be bigger than std::size_t and will, in practice, be the same type.

If you want to be sure, assert it:

static_assert(std::is_same_v<
    std::size_t,
    std::map<std::string, std::size_t>::size_type
>);

All C++ implementations in the wild I have used use the same size type for all maps.

So;

using map_size_type = std::map<int, int>::size_type;
using my_map = std::map<std::string, map_size_type>;
static_assert(std::is_same<map_size_type, my_map::size_type);

this just forces a compilation error if the (reasonable) assumption fails.

But are you sure that the size_type of a std::map depends from the key/value types?

If so, I don't see a way to get it.

But the size_type shouldn't depends from key/value types and usually is std::size_t .

I suggest

using Index0 = typename std::map<std::string, std::size_t>::size_type;

using mapIndex = typename std::map<std::string, Index0>::size_type;    

You can check you've gotten the right type with

static_assert( std::is_same_v<Index0, mapIndex>, "no right type");

The only way to break the circular dependency is to use a specific type. I recommend that you simply make map_index be a std::size_t - C++ strongly implies that a std::size_t will be assignable to the map::size_type .

What you are looking for is, generally speaking, impossible.

It's conceivable (though far-fetched) that std::map<int, long>::size_type is int and std::map<int, int>::size_type is long (and similarly for other integer types), in which case there is no possible way to satisfy std::map<int, T>::size_type being T .

Conversely, it could be that std::map<int, T>::size_type is defined as T for all T , in which case there is no unique T satisfying your "requirement".

As mentioned by several answers (and your own reference link), in practice it's unlikely to be anything else than size_t .

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