Search code examples
c++templatesc++17stdtuple

C++: Find first element of tuple that satisfies predicate


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


Solution

  • 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;
     }