1

For a std::map<K, V> the default value_type is std::pair<const K, V>. Is there a way to use a custom value_type? As far as I can tell you can't.

Edit: To be clear, a custom value_type might be something like this:

struct Edge {
  K from;
  V to;

  int calculate_thing();
  void print_debug();
};

E.g. suppose I have some existing function that I don't want to change like this:

template<typename It>
void processEdges(It begin, It end) {
   for(auto it = begin; it != end; ++it) {
     do_stuff(it->from);
     do_more_stuff(it->calculate_thing());
  }
}
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 4
    Why would you even want to customize that? Drop the `const` on the key type? Then you'd violate the type invariants. I can't think of a use case here...? – lubgr Jul 04 '19 at 12:22
  • What's your usecase here? – Tanveer Badar Jul 04 '19 at 12:23
  • 3
    what do you mean with "custom value type" exactly? You choose `K` you choose `V`, what else would you customize? – 463035818_is_not_an_ai Jul 04 '19 at 12:24
  • There are other containers with other `value_type`s. Find whatever container you're looking for, or create one yourself. – Sam Varshavchik Jul 04 '19 at 12:28
  • 1
    imho the first part of your example helps a lot to understand the question, but not the second. Because for the funtion you could simply use a different container (eg `std::vector`), not clear why it has to be a map in this case – 463035818_is_not_an_ai Jul 04 '19 at 12:51
  • It has to be a `map` so I can look `Edge`'s up by key. I mean, that's the whole point of a `map`. How much motivation do you guys need? – Timmmm Jul 04 '19 at 12:55
  • It would be a whole lot easier to have `calculate_thing(*it)`. The problem here is the desire to add methods to each node, when free functions would be easier. Sure, free functions can't access private members, but `std::pair` does not have any private members anyway. – MSalters Jul 04 '19 at 13:03
  • @Timmmm I really think you could use a `set` here, with a custom comparator that only compares the key part of your `Edge` struct. The difference between a `set` and a `map` (for this use case) is only that in a `map` the key is separate while in a `set` the key and the value are in the same type/object/structure. – Rene Jul 04 '19 at 13:07
  • @Rene: That is not the only difference, e.g. to update a value you have to remove it and then reinsert it. It's also a bit icky because `set` assumes that `!(a < b) && !(b < a)` means that `a == b` which I would be violating. It's definitely a valid option though. – Timmmm Jul 04 '19 at 13:51
  • @MSalters: I think you missed "some existing function **that I don't want to change**". – Timmmm Jul 04 '19 at 13:52
  • "How much motivation do you guys need?" as I wrote, the first part of the example is already motivation enough for me. Too often I have the key already stored with the value and feel bad for storing the key twice just to be able to use `std::map` for convenience – 463035818_is_not_an_ai Jul 04 '19 at 13:58
  • `struct Edge` - if you're looking at graphs, you might want Boost::Graph. – MSalters Jul 04 '19 at 14:06

3 Answers3

7

It's always std::pair<const K, V>, you can't change that.

If you need a custom value_type, maybe you could use std::set (preferably with a transparent comaprator).

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 2
    Yeah `std::set` is an option, but it's not quite the same - for instance if you want to change a value you have to remove it and then insert it again. Also the interface is not quite as nice. – Timmmm Jul 04 '19 at 12:41
  • 2
    @Timmmm This is also true for a map when you want to change the key! For both maps and sets you can only change the value. – Rene Jul 04 '19 at 13:03
  • @Timmmm Even though you have to remove it, you can minimzie the cost of it by using insert with hint or something like that, so `std::set` is a way to go – bartop Jul 04 '19 at 13:06
  • ...Or you could abuse `mutable` to be able to modify some parts of your objects. – HolyBlackCat Jul 04 '19 at 15:42
1

You could add a level of indirection via an adapter class. Something like below (examples adapting map, or individual map iterators, and showing don't have to change target function for vectors)...

// main.cpp
#include <iostream>
#include <map>
#include <vector>
#include <type_traits>

// https://stackoverflow.com/questions/56887842/c-map-with-custom-value-type
// From the original question
template <typename K, typename V>
struct Edge {
  K from;
  V to;

  int calculate_thing() { return 0; }
  void print_debug() { std::cout << "from='" << from << "', to='" << to << "'\n"; }
};

void do_stuff(int) {}
void do_more_stuff(int) {}

template<typename It>
void processEdges(It begin, It end) {
   for(auto it = begin; it != end; ++it) {
     do_stuff(it->from);
     do_more_stuff(it->calculate_thing());
     it->print_debug();
  }
}

// An additional level of indirection for maps
template <typename TMapIterator>
class MapValueIterator {
private:
  TMapIterator map_iterator;
  typedef typename std::iterator_traits<TMapIterator>::value_type map_iterator_type;

public:
  typedef ptrdiff_t difference_type;
  typedef typename map_iterator_type::second_type value_type;
  typedef value_type & reference;
  typedef value_type * pointer;
  typedef std::forward_iterator_tag iterator_category;

  MapValueIterator(TMapIterator map_iterator) : map_iterator(map_iterator) {}
  MapValueIterator(MapValueIterator &other) { map_iterator = other.map_iterator; }
  ~MapValueIterator() {}
  MapValueIterator& operator=(MapValueIterator &other) { map_iterator = other.map_iterator; return *this; }
  MapValueIterator operator++() { map_iterator++; return *this; }
  value_type*  operator*() const { return &map_iterator->second; }

  MapValueIterator operator++(int) {
    TMapIterator next_map_iterator(map_iterator);
    next_map_iterator++;
    return MapValueIterator(next_map_iterator);
  }

  value_type* operator->() const { return &map_iterator->second; }
  friend bool operator== (MapValueIterator const &left, MapValueIterator const &right) { return left.map_iterator == right.map_iterator; }
  friend bool operator!= (MapValueIterator const &left, MapValueIterator const &right) { return left.map_iterator != right.map_iterator; }
};

template <typename TMap>
class MapValueAdapter {
  TMap &map;
public:
  MapValueAdapter(TMap &map) : map(map) {}

  MapValueIterator<typename TMap::iterator> begin() { return MapValueIterator<typename TMap::iterator>(this->map.begin()); }
  MapValueIterator<typename TMap::iterator> end() { return MapValueIterator<typename TMap::iterator>(this->map.end()); }
};

// Map detection cribbed from https://stackoverflow.com/questions/45042233/detect-if-type-is-a-mapping
template <typename T>
struct is_pair : std::false_type { };

template <typename T, typename U>
struct is_pair<std::pair<T, U>> : std::true_type { };

template <typename T>
constexpr bool is_pair_v = is_pair<T>::value;

template <typename TContainer>
typename std::enable_if<
  is_pair_v<typename std::iterator_traits<typename TContainer::iterator>::value_type>,
  MapValueAdapter<TContainer>
>::type
  adapt(TContainer &container) { return MapValueAdapter (container); }

template <typename TIterator>
typename std::enable_if<
  is_pair_v<typename std::iterator_traits<TIterator>::value_type>,
  MapValueIterator<TIterator>
>::type
  adapt(TIterator iterator) { return MapValueIterator (iterator); }

template <typename TContainer>
typename std::enable_if<
  !is_pair_v<typename std::iterator_traits<typename TContainer::iterator>::value_type>,
  TContainer &
>::type
  adapt(TContainer &container) { return container; }

int main(void)
{
  // Example where std::map, std::map::iterator and std::vector are adapted for processEdges() call.
  std::map<int, Edge<int, char>> map;
  map.insert_or_assign(2, Edge<int, char>{2, '2'});
  map.insert_or_assign(1, Edge<int, char>{1, '1'});
  std::vector<Edge<int, char>> const vector = { { 3, '3'}, {4, '4'} };
  auto map_adapter = adapt(map);
  auto vector_adapter = adapt(vector);

  std::cout << "Starting" << std::endl;
  std::cout << "Map\n";
  processEdges(map_adapter.begin(), map_adapter.end());
  std::cout << "Map Iterator\n";
  processEdges(adapt(map.begin()), adapt(map.end()));
  std::cout << "Vector\n";
  processEdges(vector_adapter.begin(), vector_adapter.end());
  std::cout << "Finished" << std::endl;
}

The output is:

Starting
Map
from='1', to='1'
from='2', to='2'
Map Iterator
from='1', to='1'
from='2', to='2'
Vector
from='3', to='3'
from='4', to='4'
Finished
WaffleSouffle
  • 3,293
  • 2
  • 28
  • 27
-1

you can use boost variant for this

 typedef boost::variant<std::string, std::map<std::string, std::string>> Value;
typedef std::map<std::string, Value> TheMapYouWouldUse;