为何使用std::void_t与SFINAE实现的自定义can_visit类型特质编译失败
问题根因
你遇到的核心问题是 std::visit本身不是SFINAE友好的:
C++标准明确规定,当传入的访问器不能覆盖std::variant的所有可选类型时,std::visit的实例化会直接触发硬编译错误,而非返回可被SFINAE捕获的替换失败。这和你参考的can_add逻辑完全不同:普通运算符重载的不匹配属于SFINAE可捕获的替换失败,不会打断编译流程。
正确实现方案
我们可以手动遍历std::variant的所有备选类型,逐个检查访问器是否能调用每个类型,只有所有类型都能被调用时才返回true,实现如下:
#include <variant> #include <type_traits> #include <iostream> // 递归遍历variant类型的辅助实现 template <class TVisitor, class TVariant, size_t Idx = 0> struct can_visit_impl { static constexpr bool value = // 检查当前索引对应类型是否可被访问器调用 std::is_invocable_v<TVisitor&, std::variant_alternative_t<Idx, TVariant> const&> // 递归检查下一个索引的类型 && can_visit_impl<TVisitor, TVariant, Idx + 1>::value; }; // 递归终止条件:所有类型遍历完成 template <class TVisitor, class TVariant> struct can_visit_impl<TVisitor, TVariant, std::variant_size_v<TVariant>> { static constexpr bool value = true; }; // 对外暴露的类型特质 template <class TVisitor, class TVariant, class = void> struct can_visit : std::false_type {}; template <class TVisitor, class TVariant> struct can_visit<TVisitor, TVariant, std::enable_if_t<can_visit_impl<TVisitor, TVariant>::value>> : std::true_type {}; // 便捷变量模板 template <class TVisitor, class TVariant> constexpr bool can_visit_v = can_visit<TVisitor, TVariant>::value;
测试验证
用你提供的测试用例可得到预期结果:
struct A{}; struct B{}; struct C{}; using ABC = std::variant<A,B,C>; using AB = std::variant<A,B>; struct ABCVis { void operator()(A const & a){}; void operator()(B const & a){}; void operator()(C const & a){}; }; struct ABVis { void operator()(A const & a){}; void operator()(B const & a){}; }; int main(){ std::cout << can_visit_v<ABVis, ABC> << std::endl; // 输出0,符合预期 std::cout << can_visit_v<ABCVis, ABC> << std::endl; // 输出1 std::cout << can_visit_v<ABVis, AB> << std::endl; // 输出1 }
如果你实际调用
std::visit时会传非const的variant、右值访问器等参数,只需调整std::is_invocable_v里的参数值类别和实际调用逻辑对齐即可。
内容的提问来源于stack exchange,提问作者bradgonesurfing
相关产品推荐
相关产品推荐

