2

I'm converting some code from using CreateProcess to use instead. I need to replace my CreateProcess usages with boost::process::child. The problem is they have incompatible ways for me to say "I want to use the default value". What used to be a one line command has become sixteen if statements.

The function I currently use works like this (simplified):

void CreateProcess(int, float);

Each parameter had a value you could use to indicate you wanted the default. I'll use 0 in this example:

int int_param = ...;
float float_param = ...;

CreateProcess(0, 0); //Use both defaults
CreateProcess(int_param, 0); //Use default float
CreateProcess(0, float_param); //Use default int
CreateProcess(int_param, float_param); //No defaults

This is a common design. You know what I am talking about. Whether I wanted to use the default value or not could be decided with a simple conditional, such as (... ? 0 : int_param). This allowed every CreateProcess call to be a single line of code.

CreateProcess( (... ? 0 : int_param), (... ? 0 : float_param) );

Where I used to call CreateProcess, I now want to create a child class. The constructor for child works like this:

template<typename ...Args>
explicit child(Args&&...args);

Instead of passing a specific value to use a default, I have to pass nothing.

int int_param = ...;
float float_param = ...;

child c; //Use both defaults
child c(int_param); //Use default float
child c(float_param); //Use default int
child c(int_param, float_param); //No defaults
child c(float_param, int_param); //Argument order is irrelevant

So far my only "solution" is to use if branches.

if (...) {
    if (...)
        child c;
    else
        child c(float_param);
} else {
    if (...)
        child c(int_param);
    else
        child c(int_param, float_param);
}

This example has only two possible arguments and it becomes four branches. The real child has four possible arguments, so every instance has sixteen branches.

What I would like is some way of building the call to avoid this branching. A solution specific to boost::process is also fine.

FYI, the last two arguments to child may or may not be the same type.

Weak to Enuma Elish
  • 4,622
  • 3
  • 24
  • 36
darune
  • 10,480
  • 2
  • 24
  • 62
  • Sorry I don't understand. Are you asking how to rework your design to avoid branching? What does it have to do with variadic templates? (not varargs) – Lightness Races in Orbit May 29 '19 at 09:59
  • @LightnessRacesinOrbit The issue is that with first branch this seems ok, then I also to differentiate with commands (either cmd, or exe and cmd is given). Then Im at 4 different branches/type of calls. In the future, i plan to do something with the environment as well (this might not yield another branch but if it did then i would have ~8) - I hope you can see this is a lot of boilerplate branching. – darune May 29 '19 at 10:34
  • Right but what does that have to do with Boost or variadic templates? – Lightness Races in Orbit May 29 '19 at 10:35
  • @LightnessRacesinOrbit if theres a solution that works for `boost::process` in this case then that might be fine. A generel solution would also work. – darune May 29 '19 at 10:39
  • 1
    @LightnessRacesinOrbit I think OP's problem is that the old function would accept values like `nullptr` to mean _"use the default"_, but the new function requires you to either supply a valid value or not enter the argument at all to use the default. So now they are forced to convert single-line function calls to massive spanning `if-then` trees based on how many parameters they need to supply. – Weak to Enuma Elish May 29 '19 at 10:50
  • @LightnessRacesinOrbit I tried to clarify the post now – darune May 29 '19 at 10:50
  • @WeaktoEnumaElish thank you, that is spot on ! – darune May 29 '19 at 10:51
  • Why "too broad" ? I would like to know so I can narrow it down – darune May 29 '19 at 10:56
  • @WeaktoEnumaElish Good explanation. We could do with that in the question. – Lightness Races in Orbit May 29 '19 at 11:09
  • Okay, here's a follow-up question: why variadic templates? – Lightness Races in Orbit May 29 '19 at 11:09
  • Your "before" example still works and doesn't need default arguments – Lightness Races in Orbit May 29 '19 at 11:11
  • @LightnessRacesinOrbit The constructor is a variadic template function. I don't mind removing the tag though. – darune May 29 '19 at 11:12
  • @LightnessRacesinOrbit The old one works, yes, but I am trying to convert to `boost::process` (reason: i wan't to use the pipe api provided with `boost::process`) – darune May 29 '19 at 11:14
  • I don't see how you can get around the need for branching when `boost::process` requires you to use different ctors. How do you envision a solution looking? – Lightness Races in Orbit May 29 '19 at 11:16
  • @LightnessRacesinOrbit thats a good point. Maybe some expression template that allows binding one argument at a time could be an option - but Im not sure. – darune May 29 '19 at 11:21
  • Is every possible argument to `child` a different type, or does it have multiple arguments that can be the same type? – Weak to Enuma Elish May 29 '19 at 11:32
  • @WeaktoEnumaElish The last, arguments can have same (or different) type – darune May 29 '19 at 11:37
  • Maybe write the variadic function to accept `std::optional` arguments, ignoring them when empty and treating them the same as `T` arguments when not empty? – aschepler May 29 '19 at 11:41
  • 2
    (I take it "avoid branching" here means "don't need to write tons of `if` and `else` at the call site", not "avoid branching instructions at the assembly/CPU level".) – aschepler May 29 '19 at 11:43
  • @aschepler thats true,and a good idea, thing is, I cannot change the target function – darune May 29 '19 at 11:55
  • Ah, I see you've rewritten the question. This is much better now – Lightness Races in Orbit May 29 '19 at 13:53

2 Answers2

2

If you want to be able to leave out parameters in the call, and do things like:

create_child();
create_child(std::make_optional(int_param));
create_child(std::make_optional(float_param),
             (... ? std::optional<int>{} : int_param);

You can use some templates and a recursive function. The following code is an example. There are ways to make this better, like only creating one child and not returning a bunch, or using perfect forwarding.

//Recursion base case
//Recieves values to pass to child in a tuple
//Returns created child
template <typename... Ts>
child create_child_detail(std::tuple<Ts...> t)
{
    //Is there a better way to apply a tuple to a ctor?
    return std::apply([](Ts... ts){ return child(ts...); }, t);
}

//Recursive function
//Removes arguments from variadic template and adds arguments to tuple
template <typename... Ts, typename O, typename... Os>
child create_child_detail(std::tuple<Ts...> t, std::optional<O> o, std::optional<Os>... os)
{
    if (o) //Valid optional, add value to tuple to send to child
        return create_child_detail(std::tuple_cat(t, std::make_tuple(*o)), os...);
    else //Invalid optional, value should not be added to child
        return create_child_detail(t, os...);
}

//Called by user
//Just calls create_child_detail
template <typename... Ts>
child create_child(std::optional<Ts>... ts)
{
    return create_child_detail(std::tuple<>{}, ts...);
}

Empty optionals are removed. The arguments are passed to child in the order you wrote them. It can take any number of arguments. I tested it out on Coliru here so you can play with it.

Weak to Enuma Elish
  • 4,622
  • 3
  • 24
  • 36
1

Use a function to create child objects which contains the branches. Pass std::optional as parameters:

child make_child(const std::optional<int>& int_param,
                 const std::optional<float>& float_param)
{
    if (int_param && float_param) {
        return child(std::to_string(int_param.value()),
                     std::to_string(float_param.value()));
    }

    // Rest of the branches ...
}

This allows you to create a child object like this:

child c = make_child(... ? std::optional<int>() : int_param,
                     ... ? std::optional<float>() : float_param);
Nikos C.
  • 50,738
  • 9
  • 71
  • 96