我实际上对同一件事有点兴趣(想根据最终参数专门化模板化参数包)。
std::make_tuple
我相信通过组合元组反转( ,C++ 14 的后向端口std::apply
等)可能会有一条前进的道路:
如果成功的话会回到这里。
相关文章:
编辑:是的,过了一会儿想通了;不完美,因为有额外的副本飞来飞去,但这是一个开始。
如果您知道比我在下面列出的更简单的方法,请不要犹豫发布!
TL;博士
可以做这样的事情:
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
然后实现这个伪代码:
template<typename ... Args>
void my_func(Args&& ... args, const my_special_types& x);
通过执行以下操作:
template<typename... Args>
void my_func(Args&& ... args)
-> call my_func_reversed(args...)
template<typename... RevArgs>
void my_func_reversed(const my_special_types& x, RevArgs&&... revargs)
-> do separate things with revargs and my_special_types
-> sub_func_reversed(revargs...)
使用上述实用程序。
有一些(很多)缺点。将在下面列出。
范围
这是为 C++14(可能是 C++11)的用户准备的,他们想从未来(C++17)中借鉴。
第 1 步:反转论点
有几种不同的方法可以做到这一点。我在这个例子中列出了一些替代方案:
- tuple.cc - 两种选择的游乐场(源代码中的学分):
- 使用可折叠表达式并操作通过
std::apply_impl
(credit: Orient) 传递的索引。
- 使用递归模板构造一个反向的
index_sequence
(来源:Xeo)
tuple.output.txt - 示例输出
这将打印出reversed_index_sequence
Xeo 示例中的模板。我需要这个来调试。
>>> name_trait<std::make_index_sequence<5>>::name()
std::index_sequence<0, 1, 2, 3, 4>
>>> name_trait<make_reversed_index_sequence<5>>::name()
std::index_sequence<4, 3, 2, 1, 0>
我选择了备选方案 1,因为它更容易消化。然后,我尝试快速将其正式化:
std::apply
定义片段(改编自cppreference.com 上的 C++17 可能实现):
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_reversed_impl(F &&f,
Tuple &&t, std::index_sequence<I...>)
{
// @ref https://stackoverflow.com/a/31044718/7829525
// Credit: Orient
constexpr std::size_t back_index = sizeof...(I) - 1;
return f(std::get<back_index - I>(std::forward<Tuple>(t))...);
}
} // namespace detail
template <class F, class Tuple>
constexpr decltype(auto) apply_reversed(F &&f, Tuple &&t)
{
// Pass sequence by value to permit template inference
// to parse indices as parameter pack
return detail::apply_reversed_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<
std::tuple_size<std::decay_t<Tuple>>::value>{});
}
用法片段:(来自tuple_future_main.output.txt
,从上面复制)
auto my_func_callable = [] (auto&& ... args) {
return my_func(std::forward<decltype(args)>(args)...);
};
auto my_func_reversed =
stdcustom::make_callable_reversed(my_func_callable);
第 2 步:扣上你的鞋子(使用反向参数包)
首先,为您希望使用的最终参数建立模式。您必须明确列举这些,因为您只能有一个参数包。
(取自tuple_future_main.cc):
示例场景:
我们喜欢将东西添加到具有名称的容器中,形式如下:
add_item(const Item& item, const string& name, Container& c)
我们还可以构造一个具有 [非常大] 数量的重载的 Item,并且我们有方便的重载:
add_item(${ITEM_CTOR_ARGS}, const string& name, Container& c)
为此,我们可以声明以下内容:
void add_item_direct(const Item& item, const string& name, Container& c)
Item create_item(Args&&... args)
然后定义我们的通用接口:
template<typename... Args>
void add_item(Args&&... args) {
...
auto reversed = stdcustom::make_callable_reversed(callable);
reversed(std::forward<Args>(args)...);
}
template<typename ... RevArgs>
void add_item_reversed(Container& c, const string& name, RevArgs&&... revargs)
{
...
static auto ctor = VARIADIC_CALLABLE(create_item,);
...
auto item = ctor_reversed(std::forward<RevArgs>(revargs)...);
add_item_direct(item, name, c);
}
现在我们可以做类似的事情:(取自tuple_future_main.output.txt
)
>>> (add_item(Item("attribute", 12), "bob", c));
>>> (add_item("attribute", 12, "bob", c));
>>> (add_item(Item(2, 2.5, "twelve"), "george", c));
>>> (add_item(2, 2.5, "twelve", "george", c));
>>> (add_item(Item(2, 15.), "again", c));
>>> (add_item(2, 15., "again", c));
>>> c
bob - ctor3: ctor3: ctor1: attribute (12, 10)
bob - ctor3: ctor1: attribute (12, 10)
george - ctor3: ctor3: ctor2: 2, 2.5 (twelve)
george - ctor3: ctor2: 2, 2.5 (twelve)
again - ctor3: ctor3: ctor2: 2, 15 ()
again - ctor3: ctor2: 2, 15 ()
请注意额外的复制构造函数... :(
缺点
- 丑得要命
- 可能没用
- 重构你的接口可能更容易
- 但是,这可以用作过渡到更通用接口的权宜之计。
- 可能要删除的行更少。
- 特别是如果它用模板爆炸来插入你的开发过程
- 无法确定额外副本的来源。
- 你必须精心设计你的基本功能
- 您不应该尝试扩展现有功能。
- 参数包会贪婪地匹配函数
- 您要么需要明确说明您想要的每个重载,要么屈服并让可变参数包调度到您想要的功能
- 模板错误很糟糕。
- 诚然,不算太坑爹。但是很难推断您错过了可用的过载。
- 在 lambdas 中包装了许多简单的功能
- 您可能能够使用
make_reversed_index_sequence
并直接分派给该功能(在其他 SO 帖子中提到)。但这重复起来很痛苦。
去做
- 摆脱多余的副本
- 最小化对所有 lambdas 的需求
尝试对抗参数包贪婪
是否存在与左值和右值引用匹配的通用std::enable_if
匹配,并且可能处理转发兼容的隐式复制构造函数?
template<typename ... Args>
void my_func(Args&& ... args) // Greedy
void my_func(magical_ref_match<string>::type, ...)
// If this could somehow automatically snatch `const T&` and `T&&` from the parameter pack...
// And if it can be used flexible with multiple arguments, combinatorically
希望
- 也许 C++17 将支持非最终参数包参数,这样所有这些都可以被丢弃......手指交叉