c++templatesc++20template-meta-programming

Minimal approach to deducing lots of template parameters


I frequently encounter a kind of problem illustrated in the following minimal hypothetical example.

I have a library function like this:

/**
 * `shoes` can take values {0, 1, 2}
 * `isNew` can take values {false, true}
 * `color` can take values {'r', 'g', 'b'}
 */
template <int shoes, bool isNew, char color>
int coreFun (int p1, int p2, int p3) {
  return shoes + isNew + int(color) + p1 + p2 + p3; // shoes price.
}

Now, I need to write a function for clients to use:

/**
 * Here, `shoes`, `isNew` and `color` will all be strings or string views
 * given by clients.
 * 
 * `p1`, `p2`, `p3` are variables to coreFun().
 */
int clientAPI (
    auto && shoes, auto && isNew, auto && color,
    int p1, int p2, int p3
) 
{
  int rst = 0; // result.
  if (shoes == "snicker") {
    if (isNew == "yes") {
      if (color == "red") rst = coreFun <0, true, 'r'> (p1, p2, p3);
      else if (color == "green") rst = coreFun <0, true, 'g'> (p1, p2, p3);
      else rst = coreFun <0, true, 'b'> (p1, p2, p3);
    }
    else { // isNew == "no"
      if (color == "red") rst = coreFun <0, false, 'r'> (p1, p2, p3);
      else if (color == "green") rst = coreFun <0, false, 'g'> (p1, p2, p3);
      else rst = coreFun <0, false, 'b'> (p1, p2, p3);
    }
  }
  else if (shoes == "leather") {
    if (isNew == "yes") {
      if (color == "red") rst = coreFun  <1, true, 'r'> (p1, p2, p3);
      else if (color == "green") rst = coreFun <1, true, 'g'> (p1, p2, p3);
      else rst = coreFun  <1, true, 'b'> (p1, p2, p3);
    }
    else { // isNew == "no"
      if (color == "red") rst = coreFun  <1, false, 'r'> (p1, p2, p3);
      else if (color == "green") rst = coreFun <1, false, 'g'> (p1, p2, p3);
      else rst = coreFun <1, true, 'b'> (p1, p2, p3);
    } 
  }
  else { // shoes == "other"
    if (isNew == "yes") {
      if (color == "red") rst = coreFun <2, true, 'r'> (p1, p2, p3);
      else if (color == "green") rst = coreFun <2, true, 'g'> (p1, p2, p3);
      else rst = coreFun <2, true, 'b'> (p1, p2, p3);
    }
    else { // isNew == "no"
      if (color == "red") rst = coreFun <2, false, 'r'> (p1, p2, p3);
      else if (color == "green") rst = coreFun <2, false, 'g'> (p1, p2, p3);
      else rst = coreFun <2, true, 'b'> (p1, p2, p3);
    }
  }
  return rst;
}

The signature of clientAPI() shall not be changed.

My implementation of clientAPI() works but is ridiculously long. Luckily in this example I only have 18 combinations. In real life I had encountered as many as 50 such template parameters. Pain.

I intend to simplify the implementation like the following:

int clientAPI (
    auto && shoes, auto && isNew, auto && color,
    int p1, int p2, int p3
) 
{
  using namespace std;
  auto clientInputs = tuple(move(shoes), move(isNew), move(color));
  constexpr auto mapping = tuple(
    tuple(pair("sniker", 0), pair("leather", 1), pair("other", 2)),
    tuple(pair("no", false), pair("yes", true)),
    tuple(pair("red", 'r'), pair("green", 'g'), pair("blue", 'b'))
  );
  
  
  // ===========================================================================
  // How to (i) achieve compile time deduction of the 18 instances
  // using a some kind of constexpr loop over `clientInputs` and `mapping`,
  // and then (ii) execute the correct instance of coreFun() 
  // corresponding to `clientInputs`?
  //
  // If the above is impossible, can we achieve the goal using
  // std::function and some kind of compile time lookup table ?
  // ===========================================================================
}

My question is listed in the comments above. Moreover, could there be some magic function / class that can take coreFun, clientInputs and mapping as parameters and produce the price of the shoes?

Many thanks!!


Solution

  • A more compact solution:

    int clientAPI (
        std::string_view shoes, std::string_view isNew, std::string_view color,
        int p1, int p2, int p3
    ) 
    {
      auto reduce_shoes = [&]<bool i_, char c_> ()->int {
          if (shoes == "snicker") return coreFun<0, i_, c_> (p1, p2, p3);
          else if (shoes == "leather") return coreFun<1, i_, c_> (p1, p2, p3);
          else if (shoes == "other") return coreFun<2, i_, c_> (p1, p2, p3);
          else throw std::runtime_error("No such shoes");
        };
      
      auto reduce_shoes_isNew = [&]<char c_>()->int {
        if (isNew == "yes") return reduce_shoes.template operator()<true, c_> ();
        else if (isNew == "no") return reduce_shoes.template operator()<false, c_> ();
        else throw std::runtime_error("Can only be 'yes' or 'no'");
      };
      
      if (color == "red") return reduce_shoes_isNew.template operator()<'r'> ();
      else if (color == "green") return reduce_shoes_isNew.template operator()<'g'> ();
      else if (color == "blue") return reduce_shoes_isNew.template operator()<'b'> ();
      else throw std::runtime_error("No such color");
    }