2 minute read

In C#, I’ve gotten spoiled over the years with LINQ and being able to create composite keys out of tuples to in-memory, static lookup tables or objects.

In a C++ project I’m working with, I needed to do something similar. However, the project’s strict requirement to keep external dependencies out was a bit of a roadblock. Boost can easily provide tuple support, but without it, a native solution had to be found. Thankfully, it just required finding the right combination of container types.

In C# 7, I’d do something like this using a value tuple as a composite key and an array of integers for the values.

// some hardcoded values for our lookup table
var list = new Dictionary<(int,int), int[]>() {
  { (1,10), new []{1,2,3} },
  { (1,11), new []{4,5,6} },
  { (1,12), new []{8,2,1} }
};

// coming in from some other source, perhaps an api lookup
var key1 = 2;
var key2 = 10;

int[] lookup = {};
list.TryGetValue((key1,key2), out lookup);

if (lookup != null) {			
  foreach (var val in lookup) {
    // do useful things with the data, for now; print it out
    Console.WriteLine(val);
  }
}

Doing the same thing is possible in C++ (std:c++17) using maps, pairs, and vectors.

  • maps provide us with our key/value pair container.
  • pairs provide us with a two component typed object.
  • vectors provide us with our dynamically sized container to hold our maps containers.

Let’s include <map> and <vector> in our file.

#include <map>
#include <vector>

// some hardcoded values for our lookup table
std::map<std::pair<int,int>, std::vector<int>> list = {
  {{1,10}, {1,2,3}}, // { {key1, key2}, { values } }
  {{1,11}, {4,5,6}},
  {{1,12}, {8,2,1}}
}

// coming in from some other source, perhaps an api lookup
auto key1 = 2;
auto key2 = 10;

try {
  std::vector<int> lookup = list.at({ key1, key2});
  // do useful things with the data, for now; print it out.
  for (const auto &val : lookup) {
    std::cout << &val << "\n";
  }
} catch (const std::out_of_range &ex) {
  // ¯\_(ツ)_/¯
  // could check length and index first, but at least this
  // throws the proper error for logging, etc.
}

Once the types were sorted out, it lacks some of the syntactical sugar of C#, but codes out just the same.

Using list.at({key1,key2}) has the same complexity cost as operator[] operations; however, rather than receiving an undefined error, we’ll receive a much safer std::out_of_range exception if the key isn’t found.

comments powered by Disqus