使用std::apply结合emplace_back展开参数包编译失败问题排查
问题:使用std::apply展开tuple调用emplace_back编译失败
尝试在模板函数中向容器内emplace新对象,函数接收包含对象构造所需参数的std::tuple,因此使用std::apply和折叠表达式来展开tuple中的值,但代码无法编译。等价代码如下:
#include <vector> struct S { S(float restAngle, bool useIt) : restAngle(restAngle) , useIt(useIt) {} float restAngle; bool useIt; }; int main() { std::tuple t(0.0F, false); std::vector<S> ss; std::apply([&ss](auto ...data) {ss.emplace_back((data,...));}, t); }
在GCC 13中编译时出现如下错误:
In file included from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/x86_64-linux-gnu/bits/c++allocator.h:33, from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/allocator.h:46, from /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/vector:63, from <source>:1: /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/new_allocator.h: In instantiation of 'void std::__new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {bool&}; _Tp = S]': /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/alloc_traits.h:537:17: required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(allocator_type&, _Up*, _Args&& ...) [with _Up = S; _Args = {bool&}; _Tp = S; allocator_type = std::allocator<S>]' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/vector.tcc:117:30: required from 'std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {bool&}; _Tp = S; _Alloc = std::allocator<S>; reference = S&]' <source>:18:52: required from 'main()::<lambda(auto:1 ...)> [with auto:1 = {float, bool}]' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:2558:26: required by substitution of 'template<class _Fn, class ... _Args> static std::__result_of_success<decltype (declval<_Fn>()((declval<_Args>)()...)), std::__invoke_other> std::__result_of_other_impl::_S_test(int) [with _Fn = main()::<lambda(auto:1 ...)>; _Args = {float&, bool&}]' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:2569:55: [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ] /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:161:35: recursively required by substitution of 'template<class _Result, class _Ret> struct std::__is_invocable_impl<_Result, _Ret, true, std::__void_t<typename _CTp::type> > [with _Result = std::__invoke_result<main()::<lambda(auto:1 ...)>, float&, bool&>; _Ret = void]' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:161:35: required by substitution of 'template<class ... _Bn> std::__detail::__first_t<std::integral_constant<bool, true>, typename std::enable_if<(bool)(_Bn::value), void>::type ...> std::__detail::__and_fn(int) [with _Bn = {std::__is_invocable_impl<std::__invoke_result<main()::<lambda(auto:1 ...)>, float&, bool&>, void, true, void>, std::__call_is_nothrow<std::__invoke_result<main()::<lambda(auto:1 ...)>, float&, bool&>, main()::<lambda(auto:1 ...)>, float&, bool&>}]' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:177:42: required from 'struct std::__and_<std::__is_invocable_impl<std::__invoke_result<main()::<lambda(auto:1 ...)>, float&, bool&>, void, true, void>, std::__call_is_nothrow<std::__invoke_result<main()::<lambda(auto:1 ...)>, float&, bool&>, main()::<lambda(auto:1 ...)>, float&, bool&> >' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/type_traits:3103:12: required from 'struct std::is_nothrow_invocable<main()::<lambda(auto:1 ...)>, float&, bool&>' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/tuple:2272:31: required from 'constexpr const bool std::__unpack_std_tuple<template<class _Fn, class ... _ArgTypes> struct std::is_nothrow_invocable, main()::<lambda(auto:1 ...)>, std::tuple<float, bool>&>' /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/tuple:2295:14: required from 'constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = main()::<lambda(auto:1 ...)>; _Tuple = tuple<float, bool>&]' <source>:18:15: required from here /opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/new_allocator.h:187:11: error: no matching function for call to 'S::S(bool&)' 187 | { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); } | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:5:5: note: candidate: 'S::S(float, bool)' 5 | S(float restAngle, bool useIt) | ^ <source>:5:5: note: candidate expects 2 arguments, 1 provided <source>:3:8: note: candidate: 'constexpr S::S(const S&)' 3 | struct S | ^ <source>:3:8: note: no known conversion for argument 1 from 'bool' to 'const S&' <source>:3:8: note: candidate: 'constexpr S::S(S&&)' <source>:3:8: note: no known conversion for argument 1 from 'bool' to 'S&&'
问题原因与修复
原因
你写的(data,...)是逗号表达式,它会依次计算每个data表达式,但最终只返回最后一个表达式的值(也就是tuple里的最后一个元素false)。这就导致emplace_back实际只收到了一个bool参数,完全匹配不上需要float和bool两个参数的S构造函数,所以编译器报错。
修复方法
不需要使用折叠表达式,直接把参数包展开传递给emplace_back即可——emplace_back本身就是可变参数模板,支持直接接收多个参数并转发给对象的构造函数。只需要把(data,...)改成data...:
#include <vector> #include <tuple> // 注意:原代码遗漏了tuple头文件,标准中必须显式包含 struct S { S(float restAngle, bool useIt) : restAngle(restAngle) , useIt(useIt) {} float restAngle; bool useIt; }; int main() { std::tuple t(0.0F, false); std::vector<S> ss; std::apply([&ss](auto ...data) { ss.emplace_back(data...); }, t); }
额外说明
- 原代码遗漏了
<tuple>头文件:虽然部分编译器会通过其他标准头间接引入它,但按照C++标准,使用std::tuple和std::apply必须显式包含<tuple>,否则代码不具备可移植性。 - 折叠表达式在这里完全多余:
std::apply已经把tuple的元素展开成了参数包data...,直接传递给emplace_back就会把每个元素作为独立参数转发,正好匹配S的构造函数。
内容的提问来源于stack exchange,提问作者jjcasmar
相关产品推荐
相关产品推荐

