5

I have the following verbose code:

struct thing1 { int key, std::string value; };
struct thing2 { int key, std::string value; };
// ...
struct thingN { int key, std::string value; };

struct thing_map {
  thing1 t1;
  thing2 t2;
  // ...
  thingN tN;

  std::string get(int key) {
    if(t1.key == key) return t1.value;
    if(t2.key == key) return t2.value;
    // ...
    if(tN.key == key) return tN.value;
    throw std::runtime_error("bad key");
  }
};

I can refactor the things to be an std::tuple<thing1, thing2, /* ... */ thingN>, this allows me access them with a typed std::get, so no functionality is lost (i.e. std::get<thing1>(things)). I can't figure out how implement the if cascade though. There are various implementations of functions which apply a function to each tuple element around the internet, but these functions always use an index parameter pack to do the mapping so I cannot select a single element and return its value. The trivial thing to do is maybe to save the tN.value to a captured variable and return that, but I have a feeling there is a better solution.

For clarity, what I am trying to do is this:

struct thing_map {
  std::tuple<thing1, thing2, /* ... */ thingN> things;

  std::string get(int key) {
    foreach(auto&& thing : things) {
      if (key == thing.key) return thing.value;
    }
    throw std::runtime_error("bad key");
  }
};

I am using C++17

JeJo
  • 30,635
  • 6
  • 49
  • 88
Ron
  • 1,989
  • 2
  • 17
  • 33
  • 1
    C++11, C++14 or C++17? – max66 Apr 20 '19 at 16:19
  • 2
    Why have different thingN types when they are all the same? If they at least had a common base class, you could use that to loop rather than a different unrelated type for every element. – Chris Dodd Apr 20 '19 at 16:21
  • This is just a contrived example, the different things are actually different. @max66 C++17, edited into the post. – Ron Apr 20 '19 at 16:29
  • if all the "things" you're using have a key variable in common i really don't see why you can't make a parent class with the key variable, and then make a list of pointers to the parent class; that way you can check the key value regardless of what child class the thing you find belongs to. – Barnack Apr 20 '19 at 16:40
  • @Barnack, You lose value semantics. In the case of each thing being different, you might also lose the ability to return the exact value type. Those pointers also have to point to something which is stored somewhere stable. – chris Apr 20 '19 at 16:42

1 Answers1

9

You can use C++17 so I propose the use of std::apply() and template folding as follows

   std::string get(int key)
    {
      return std::apply([&](auto const & ... args)
       {
         std::string ret;

         ( ((key == args.key) ? (ret = args.value, true) : false)
           || ... || (throw std::runtime_error("bad key"), false) );

         return ret;
       }, things);
    }

The following is a full compiling example

#include <tuple>
#include <string>
#include <iostream>
#include <stdexcept>

struct thing1 { int key{1}; std::string value{"one"}; };
struct thing2 { int key{2}; std::string value{"two"}; };
struct thing3 { int key{3}; std::string value{"three"}; };
struct thing4 { int key{4}; std::string value{"four"}; };

struct thing_map
 {
   std::tuple<thing1, thing2, thing3, thing4> things;

   std::string get(int key)
    {
      return std::apply([&](auto const & ... args)
       {
         std::string ret;

         ( ((key == args.key) ? (ret = args.value, true) : false)
           || ... || (throw std::runtime_error("bad key"), false) );

         return ret;
       }, things);
    }
 };

int main ()
 {
   thing_map tm;

   std::cout << tm.get(1) << std::endl;
   std::cout << tm.get(2) << std::endl;
   std::cout << tm.get(3) << std::endl;
   std::cout << tm.get(4) << std::endl;
   std::cout << tm.get(5) << std::endl;
 }
max66
  • 65,235
  • 10
  • 71
  • 111