如何避免参数数量取决于条件的调用分支?
How to avoid branching for a call where the number of arguments is dependent on conditionals?
我正在将一些代码从使用 CreateProcess
转换为使用 boost-process。我需要用 boost::process::child
替换我的 CreateProcess
用法。问题是他们有不兼容的方式让我说 "I want to use the default value"。以前的一行命令变成了十六条 if
语句。
我目前使用的函数是这样的(简化):
void CreateProcess(int, float);
每个参数都有一个值,您可以使用该值来表示您想要默认值。我将在此示例中使用 0:
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
这是一个常见的设计。你知道我在说什么。是否要使用默认值可以用一个简单的条件来决定,比如(... ? 0 : int_param)
。这允许每个 CreateProcess
调用都是一行代码。
CreateProcess( (... ? 0 : int_param), (... ? 0 : float_param) );
我以前叫CreateProcess
的地方,现在想创建一个child
class。 child
的构造函数是这样工作的:
template<typename ...Args>
explicit child(Args&&...args);
我必须不传递任何内容。
,而不是传递特定值以使用默认值
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
到目前为止我唯一的 "solution" 是使用 if
个分支。
if (...) {
if (...)
child c;
else
child c(float_param);
} else {
if (...)
child c(int_param);
else
child c(int_param, float_param);
}
这个例子只有两个可能的参数,它变成了四个分支。 真正的child
有四个可能的参数,所以每个实例都有十六个分支。
我想要的是某种构建调用以避免这种分支的方法。特定于 boost::process
的解决方案也可以。
仅供参考,child
的最后两个参数可能是也可能不是同一类型。
使用函数创建 child
个包含分支的对象。将 std::optional
作为参数传递:
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 ...
}
这允许您像这样创建一个 child
对象:
child c = make_child(... ? std::optional<int>() : int_param,
... ? std::optional<float>() : float_param);
如果您希望能够在调用中省略参数,并执行以下操作:
create_child();
create_child(std::make_optional(int_param));
create_child(std::make_optional(float_param),
(... ? std::optional<int>{} : int_param);
您可以使用一些模板和一个递归函数。下面的代码是一个例子。有一些方法可以让它变得更好,比如只创建一个 child
而不是返回一堆,或者使用完美转发。
//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...);
}
删除了空的可选值。参数按照您编写的顺序传递给 child
。它可以接受任意数量的参数。我测试了它 on Coliru here 所以你可以玩它。
我正在将一些代码从使用 CreateProcess
转换为使用 boost-process。我需要用 boost::process::child
替换我的 CreateProcess
用法。问题是他们有不兼容的方式让我说 "I want to use the default value"。以前的一行命令变成了十六条 if
语句。
我目前使用的函数是这样的(简化):
void CreateProcess(int, float);
每个参数都有一个值,您可以使用该值来表示您想要默认值。我将在此示例中使用 0:
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
这是一个常见的设计。你知道我在说什么。是否要使用默认值可以用一个简单的条件来决定,比如(... ? 0 : int_param)
。这允许每个 CreateProcess
调用都是一行代码。
CreateProcess( (... ? 0 : int_param), (... ? 0 : float_param) );
我以前叫CreateProcess
的地方,现在想创建一个child
class。 child
的构造函数是这样工作的:
template<typename ...Args>
explicit child(Args&&...args);
我必须不传递任何内容。
,而不是传递特定值以使用默认值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
到目前为止我唯一的 "solution" 是使用 if
个分支。
if (...) {
if (...)
child c;
else
child c(float_param);
} else {
if (...)
child c(int_param);
else
child c(int_param, float_param);
}
这个例子只有两个可能的参数,它变成了四个分支。 真正的child
有四个可能的参数,所以每个实例都有十六个分支。
我想要的是某种构建调用以避免这种分支的方法。特定于 boost::process
的解决方案也可以。
仅供参考,child
的最后两个参数可能是也可能不是同一类型。
使用函数创建 child
个包含分支的对象。将 std::optional
作为参数传递:
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 ...
}
这允许您像这样创建一个 child
对象:
child c = make_child(... ? std::optional<int>() : int_param,
... ? std::optional<float>() : float_param);
如果您希望能够在调用中省略参数,并执行以下操作:
create_child();
create_child(std::make_optional(int_param));
create_child(std::make_optional(float_param),
(... ? std::optional<int>{} : int_param);
您可以使用一些模板和一个递归函数。下面的代码是一个例子。有一些方法可以让它变得更好,比如只创建一个 child
而不是返回一堆,或者使用完美转发。
//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...);
}
删除了空的可选值。参数按照您编写的顺序传递给 child
。它可以接受任意数量的参数。我测试了它 on Coliru here 所以你可以玩它。