Search code examples
c++templatesvariadic-templatestemplate-meta-programming

Avoid "recursive" function calls while unpacking variadic templates until runtime condition


Basically, I want to find a template in a parameter pack that satisfies some runtime conditions. Intuitively, I just want to iterate over my instantiations of the parameter pack and find the first which satisfies a condition. My current simplified toy implementation to demonstrate what I mean:

Find the struct of X and Y which satisfies their test() first.

struct X {
  bool test(int i) {
    flag = i > 10;
    return flag;
  }
  bool flag;
  std::string value = "X satisfied first";
};

struct Y {
  bool test(int i) {
    flag = i > 11;
    return flag;
  }
  bool flag;
  std::string value = "Y satiesfied first";
};

This struct finds the first struct of X and Y to satisfy the condition. In this example it increases an integer up to a given limit until one of the structs reports that its test() was successful.

template <typename... Ts> struct FindFirst {
  static std::string find_first(int limit) {
    return find_first_satisfying(limit, Ts{}...);
  }

  static std::string find_first_satisfying(int limit, Ts... ts) {
    int i = 0;
    bool satisfied = false;
    while (i < limit && !satisfied) {
      satisfied = (ts.test(i) || ...);
      i++;
    }
    return extract(ts...);
  }

  template <typename T, typename... OtherTs>
  static std::string extract(T t, OtherTs... ts) {
    if (t.flag) {
      return t.value;
    } else {
      if constexpr (sizeof...(OtherTs) > 0) {
        return extract(ts...);
      } else {
        return "Nobody satiesfied condition";
      }
    }
  }
};

This implementation generates as many different extract() functions with different signatures as there are templates in the pack. They get "recursively" called and result in a deep call stack (depends on the position of the satisfying struct) and large bytecode.

Is there a method to construct a loop (at compile-time) which tests each instantiation of the parameter pack and stops appropriately? Also, any other suggestions on how to simplify the whole construct?


Solution

  • I would wrote your code something like that:

    template <typename ... Ts>
    std::string find_first_satisfying(int limit, Ts... ts)
    {
        for (int i = 0; i != limit; ++i) {
          std::string res;
          bool found = false;
          ([&](){ if (ts.test(i)) { found = true; res = ts.value; } return found;}() || ...);
          if (found) { return res; }
        }
        return "Nobody satisfied condition";
    }
    

    Demo