为何std::visit的lambda参数用decltype声明引用会编译失败?
需要对两个存储相同类型的std::variant对象调用std::visit(),下面这段代码可以正常编译:
std::variant<int, float> var1 = get_variant(); std::variant<int, float> var2 = get_variant(); std::visit([](auto val1, decltype(val1) val2) { // ... }, var1, var2);
但改用引用实现相同逻辑时编译失败:
std::visit([](auto&& rref1, decltype(rref1) rref2) { // ... }, var1, var2);
这么做的目的是让编译器只生成对应相同类型组合的lambda实例(比如(int, int)、(float, float))——实际程序中variant包含10种类型,希望只生成10个函数而非100种组合。困惑的是为什么引用版本无法编译。
编辑 - 编译器输出(gcc 9.4)
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant: In instantiation of ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::__visit_invoke_impl(_Visitor&&, _Variants ...) [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’:
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:989:28: required from ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::__do_visit_invoke(_Visitor&&, _Variants ...) [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:1005:28: required from ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::__visit_invoke(_Visitor&&, _Variants ...) [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:1014:28: required from ‘static constexpr auto std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...)>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply() [with bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {0, 1}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:909:48: recursively required from ‘static constexpr void std::__detail::__variant::__gen_vtable_impl<__same_return_types, std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...), __dimensions ...>, std::tuple<_Variants ...>, std::integer_sequence<long unsigned int, __indices ...> >::_S_apply_single_alt(_Tp&, _Tp) [with bool __do_cookie = false; long unsigned int __index = 0; _Tp = std::__detail::__variant::_Multi_array<void (*)(main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&, std::variant<int, float>&, std::variant<int, float>&), 2>; bool __same_return_types = true; _Result_type = void; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&; long unsigned int ...__dimensions = {2, 2}; _Variants = {std::variant<int, float>&, std::variant<int, float>&}; long unsigned int ...__indices = {}]’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:909:48: required from ‘constexpr const _Array_type std::__detail::__variant::__gen_vtable<true, void, main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>&&, std::variant<int, float>&, std::variant<int, float>&>::_S_vtable’
/opt/ohpc/pub/compiler/gcc/9.4.0/include/c++/9.4.0/variant:1647:23: required from ‘constexpr decltype(auto) std::__do_visit(_Visitor&&, _Variants&& ...) [with bool __use_index = false; bool __same_return_types = true; _Visitor = main(int32_t, const char**)::<lambda(auto:23&&, decltype (rref1))>; _Variants = {std::variant<int, float>&, std::variant<int, float>&}]’
原因分析
问题出在decltype(rref1)推导的类型上:
当使用auto&&作为参数时,C++的引用折叠规则会让rref1的类型根据传入值的类别变化——比如访问左值var1中的int时,rref1的类型是int&。但std::visit在尝试匹配第二个variant的类型时,会尝试传入不同引用类别的参数(比如int&&、const int&),而你的lambda要求第二个参数必须和rref1的完全相同类型(包括引用类别),这就导致类型不匹配,触发编译错误。
而值传递版本中,decltype(val1)是纯值类型(比如int),不管传入的是左值还是右值,都会被拷贝为值类型,所以类型匹配不会有问题。
解决方法
要实现“引用传递且约束两个参数类型相同”的需求,可以用以下几种方式:
方法1:移除引用类别后重新添加引用(兼容C++17)
#include <type_traits> std::visit([](auto&& rref1, std::remove_reference_t<decltype(rref1)>& rref2) { // ... }, var1, var2);
这种方式会让编译器只生成同类型的lambda实例,和你原本的需求完全一致。
方法2:用C++20的std::same_as约束类型
#include <concepts> std::visit([](auto&& rref1, auto&& rref2) requires std::same_as<std::remove_reference_t<decltype(rref1)>, std::remove_reference_t<decltype(rref2)>> { // ... }, var1, var2);
如果variant类型不匹配,会直接触发编译错误。
方法3:C++17的static_assert检查
#include <type_traits> std::visit([](auto&& rref1, auto&& rref2) { static_assert(std::is_same_v<std::remove_reference_t<decltype(rref1)>, std::remove_reference_t<decltype(rref2)>>, "两个variant存储的类型必须相同"); // ... }, var1, var2);
这种方式会在编译阶段检查类型是否匹配,不匹配则抛出断言错误。
内容的提问来源于stack exchange,提问作者Phiv

