user2961927
user2961927

Reputation: 1728

Minimal approach to deduce 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!!

Upvotes: 0

Views: 115

Answers (2)

user2961927
user2961927

Reputation: 1728

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");
}

Upvotes: 1

Jarod42
Jarod42

Reputation: 218138

You can use std::variant and std::integral_constant to do the conversion only once by type:

std::variant<std::false_type, std::true_type>
to_variant(bool b)
{
    switch (b) {
        case false: return std::false_type{};
        case true: return std::true_type{};
    }
}

std::variant<
    std::integral_constant<char, 'r'>,
    std::integral_constant<char, 'g'>,
    std::integral_constant<char, 'b'>
>
to_variant_color(std::string_view color)
{
    if (color == "red") {
        return std::integral_constant<char, 'r'>{};
    } else if (color == "green") {
        return std::integral_constant<char, 'g'>{};
    } else if (color == "blue") {
        return std::integral_constant<char, 'b'>{};
    }
    throw std::runtime_error("Invalid color");
}

std::variant<
    std::integral_constant<int, 0>,
    std::integral_constant<int, 1>,
    std::integral_constant<int, 2>
>
to_variant_shoes(std::string_view shoes)
{
    if (shoes == "snicker") {
        return std::integral_constant<int, 0>{};
    } else if (shoes == "leather") {
        return std::integral_constant<int, 1>{};
    } else if (shoes == "other") {
        return std::integral_constant<int, 2>{};
    }
    throw std::runtime_error("Invalid shoes");
}

Then, let std::visit do the dispatch:

int clientAPI (
    auto && shoes_str, auto && isNew_str, auto && color_str,
    int p1, int p2, int p3
)
{
    return std::visit(
        [&](auto shoes, auto isNew, auto color){
            return fun<shoes(), isNew(), color()>(p1, p2, p3)
        },
        to_variant_shoes(shoes_str),
        to_variant(isNew_str == std::string_view("true")),
        to_variant_color(color_str)
    );
}

Demo

Upvotes: 3

Related Questions