带有包扩展的variablesfunction模板不在最后一个参数中
我想知道为什么下面的代码不能编译:
struct S { template <typename... T> S(T..., int); }; S c{0, 0};
这段代码无法使用clang和GCC 4.8进行编译。 这是clang的错误:
test.cpp:7:3: error: no matching constructor for initialization of 'S' S c{0, 0}; ^~~~~~~ test.cpp:4:5: note: candidate constructor not viable: requires 1 argument, but 2 were provided S(T..., int); ^
在我看来,这应该起作用,而T应该被推断为是一包长度为1的包。
如果标准禁止这样做,有没有人知道为什么?
因为当函数参数包不是最后一个参数时,模板参数包就不能从中推导出来,模板参数的扣除将被忽略。
因此,两个参数0, 0
与, int
进行比较,产生不匹配。
像这样的演绎规则需要覆盖很多特殊的情况(比如当两个参数包相邻时出现的情况)。 由于参数包是C ++ 11中的一个新function,各个提议的作者都保守地起草了规则。
请注意,如果没有另行推导,尾随的模板参数包将为空。 所以当你用一个参数调用构造函数时,事情就会起作用(注意模板参数包和函数参数包在这里的区别,前者是后者,后者不是)。
所以,应该有一个解决方法。 沿着这些线路的东西:
// Extract the last type in a parameter pack. // 0, the empty pack has no last type (only called if 1 and 2+ don't match) template<typename... Ts> struct last_type {}; // 2+ in pack, recurse: template<typename T0, typename T1, typename... Ts> struct last_type<T0, T1, Ts...>:last_type<T1, Ts...>{}; // Length 1, last type is only type: template<typename T0> struct last_type<T0> { typedef T0 type; }; struct S { // We accept any number of arguments // So long as the type of the last argument is an int // probably needs some std::decay to work right (ie, to implicitly work out that // the last argument is an int, and not a const int& or whatever) template <typename... T, typename=typename std::enable_if<std::is_same<int, typename last_type<T...>::type>>::type> S(T...); };
我们检查参数包的最后一个types是int
还是我们只传递了一个int
。
实际上,我对同样的事情感兴趣(希望根据最终的参数专门化模板化的参数包)。
我相信有可能通过组合元组反转( std::make_tuple
,back-port std::apply
for C ++ 14等)来实现一个path:
- 如何颠倒variadic模板函数的参数的顺序?
如果成功的话,会回到这里。
相关文章:
- 参数打包之后的参数
- 参数包后带有非推导types的参数
编辑 :是的,找了一下后, 不完美,因为有额外的副本飞来飞去,但这是一个开始。
如果您知道比我下面列出的更简单的方式,请不要犹豫,发布!
TL; DR
可以做这样的事情:
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);
然后实现这个pseduo代码:
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 – Playground有两个select(源代码中的学分):
- 使用可折叠expression式并操作通过
std::apply_impl
(credit:Orient)传递的索引。 - 使用recursion模板构造一个反向的
index_sequence
(credit:Xeo)
- 使用可折叠expression式并操作通过
-
tuple.output.txt – 示例输出
-
这将打印Xeo示例中的
reversed_index_sequence
模板。 我需要这个进行debugging。>>> 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>
-
我select了select1,因为我更容易消化。 然后,我试图快速正式确定它:
- tuple_future.h – 从未来借用(
namespace stdfuture
),并做一个扩展(namespace stdcustom
) - tuple_future_main.cc – 使用上面的简单,高级和有用的(见下文)示例
- tuple_future_main.output.txt – 示例输出
定义片段(cppreference.com上适应C ++ 17可能的std::apply
实现):
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:扣上你的鞋子(带有相反的参数包)
首先,build立你希望使用的最终参数的模式。 你将不得不明确地枚举这些,因为你只能有一个参数包。
(取自tuple_future_main.cc ):
示例scheme:
我们喜欢用名字来添加东西,比如:
add_item(const Item& item, const string& name, Container& c)
我们也可以构造一个带有[非常大的]重载的项目,并且我们拥有重载:
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 ()
请注意额外的复制构造函数… 🙁
缺点
- 丑陋地狱
- 可能没有用处
- 重构您的界面可能会更容易
- 但是,这可以被用作转换到一个更通用的接口的一个障碍。
- 可能更less的行要删除。
- 特别是如果它将你的开发过程与模板爆炸联系起来
- 重构您的界面可能会更容易
- 无法确定额外副本的来源。
- 这可能是由于明智地使用可变的lambda
- 你必须精心制作你的基本function
- 您不应该尝试扩展现有的function。
- 参数包在与function匹配方面会很贪婪
- 你需要明确地说出你想要的每一个过载,或者向下屈服,让可变参数包调度到你想要的function
- 如果你find一个优雅的方式,请让我知道。
- 模板错误很糟糕。
- 当然,不要太低劣。 但很难推断你错过了一个可用的过载。
- 在lambdas中包装了很多简单的function
- 您可以使用
make_reversed_index_sequence
并直接发送到函数(在其他SOpost中提到)。 但是这很难重复。
- 您可以使用
去做
- 摆脱额外的副本
- 最大限度地减less对所有lambda的需求
- 如果您有
Callable
不需要
- 如果您有
-
尝试打击参数包贪婪
-
是否有一个广义的
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将支持非最终的参数包参数,所有这些都可以被丢弃
从标准N3376的工作草案§14.1是一个可能的部分阅读这个。
以下是第14.1.11节
如果类模板或别名模板的模板参数具有默认的模板参数,则每个后续的模板参数应该具有提供的默认模板参数或者是模板参数包。 如果主类模板或别名模板的模板参数是模板参数包,则它应该是最后一个模板参数。 除非可以从函数模板的参数types列表中推导出模板参数,或者具有默认参数,否则函数模板的模板参数包不应该跟随其他模板参数。