使用std::visit重载模式时引用传递struct A编译失败的问题
尝试使用重载模式结合std::visit处理std::variant,但在以引用方式处理自定义结构体A<int>时出现编译错误。测试代码如下:
#include <variant> #include <iostream> template<class... Ts> struct overload : Ts... { using Ts::operator()...; consteval void operator()(auto&) const { static_assert(false, "ERROR"); } }; template<typename T, typename...> struct A {}; int main() { const std::variant<int, bool, A<int>> v{true}; std::visit(overload{ [](A<int> val) { /* works */ }, //[](A<int>& val) { /* does not work */ }, [](int val) { std::cout << val; }, [](bool val) { std::cout << std::boolalpha << val; }, }, v); }
编译错误信息(GCC 11环境):
error: static assertion failed: ERROR
11 | static_assert(false, "ERROR");
| ^~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/invoke.h:61:14: note: in instantiation of function template specialization 'overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)>::operator()' requested here
56 | { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); }
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/invoke.h:96:19: note: in instantiation of function template specialization 'std::__invoke_impl<void, overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)>, const A &>' requested here
91 | return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn),
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:1016:16: note: in instantiation of function template specialization 'std::__invoke<overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)>, const A &>' requested here
1011 | return std::__invoke(std::forward<_Visitor>(__visitor),
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:1032:14: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void ()(overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)> &&, const std::variant<int, bool, A> &)>, std::integer_sequence<unsigned long, 2>>>::__visit_invoke' requested here
1027 | decltype(__visit_invoke(std::declval<_Visitor>(),
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:968:48: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void ()(overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)> &&, const std::variant<int, bool, A> &)>, std::integer_sequence<unsigned long, 2>>>::_S_apply' requested here
963 | std::index_sequence<__indices..., __index>>::_S_apply();
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:947:7: note: (skipping 1 context in backtrace; use -ftemplate-backtrace-limit=0 to see all)
942 | (_S_apply_single_alt<false, __var_indices>(
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:932:2: note: in instantiation of function template specialization 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void ()(overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)> &&, const std::variant<int, bool, A> &), 3>, std::integer_sequence>>::_S_apply_all_alts<0UL, 1UL, 2UL>' requested here
927 | _S_apply_all_alts(
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:1055:59: note: in instantiation of member function 'std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<std::__detail::__variant::__deduce_visit_result<void ()(overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)> &&, const std::variant<int, bool, A> &), 3>, std::integer_sequence>>::_S_apply' requested here
1050 | = __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:1724:45: note: in instantiation of static data member 'std::__detail::__variant::__gen_vtable<std::__detail::__variant::__deduce_visit_result<void, overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)> &&, const std::variant<int, bool, A> &>>::_S_vtable' requested here
1719 | _Result_type, _Visitor&&, _Variants&&...>::_S_vtable;
| ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/variant:1761:18: note: in instantiation of function template specialization 'std::__do_visit<std::__detail::__variant::__deduce_visit_result<void, overload<(lambda at prog_joined.cpp:28:18), (lambda at prog_joined.cpp:30:18), (lambda at prog_joined.cpp:31:18)>, const std::variant<int, bool, A> &>>' requested here
1756 | return std::__do_visit<_Tag>(
| ^
Line 22: Char 8: note: in instantiation of function template specialization 'std::visit<overload<(lambda at solution.cpp:28:18), (lambda at solution.cpp:30:18), (lambda at solution.cpp:31:18)>, const std::variant<int, bool, A> &>' requested here
22 | std::visit(overload{
| ^
- const引用不匹配:代码中的
std::variant是const类型,std::visit访问其内部值时会传递const A<int>&类型的参数。但启用的lambda是[](A<int>& val),无法绑定到const引用,导致重载决议失败。 - 兜底operator()被实例化:当没有匹配的lambda时,编译器会尝试匹配
overload结构体中的兜底模板operator()(auto&)。由于static_assert(false)是无条件触发的,无论是否实际调用该函数,只要模板被实例化就会触发编译错误。GCC 11在处理std::visit的vtable生成时,会对variant的所有可能类型进行重载决议检查,即使当前variant存储的是bool,仍然会尝试为A<int>类型匹配visitor,最终实例化了兜底的operator()导致错误。
针对GCC 11环境,有两种可行的修改方式:
方式一:修改lambda参数为const引用
将处理A<int>的lambda参数改为const A<int>&,匹配const variant传递的参数类型:
std::visit(overload{ [](const A<int>& val) { /* 现在可以正常工作 */ }, [](int val) { std::cout << val; }, [](bool val) { std::cout << std::boolalpha << val; }, }, v);
方式二:改进兜底operator()的SFINAE约束
让兜底的operator()仅在没有其他匹配重载时才参与决议,避免被不必要地实例化。可以通过requires约束实现:
template<class... Ts> struct overload : Ts... { using Ts::operator()...; template<typename T> consteval void operator()(T&&) const requires (!std::is_invocable_v<const overload&, T&&>) { static_assert(false, "ERROR"); } };
这样只有当其他lambda都无法匹配参数类型时,兜底的operator()才会被考虑,避免在std::visit的vtable生成阶段被实例化触发错误。如果需要处理非const的variant,也可以保留原有的引用参数,调整约束即可。
内容的提问来源于stack exchange,提问作者Peter

