C++模板类型对象如何选择适配调用方法及实现原理
问题背景
我需要实现如下形式的通用调用逻辑:
std::string invoke(auto &&x) { return to_string(x) }
但适用于参数x的正确函数取决于x的实际类型,因此需要编写按优先级顺序匹配的分支判断逻辑,伪代码如下:
if (invoke方法可应用于对象x) { return invoke(x) } else if (invoke_2方法可应用于对象x) { return invoke_2(x) } ... (实际场景中存在多个待匹配的调用方法)
目前已知可以使用SFINAE检查指定方法是否可作用于目标对象,但该方案需要编写大量重复代码,使用便捷性较差,希望找到可以避免代码冗余的实现方案。
现有实现
目前已经编写了可正常运行的实现代码,当存在匹配的to_string()方法时会进入对应分支,否则该分支判定失败,会继续选择后续其他分支,但不清楚该实现能够生效的原理,代码如下:
template <typename F, typename... Args, typename = std::void_t<decltype(std::declval<F>()(std::declval<Args&&>()...))>> std::true_type IsImpl(nullptr_t ); template <typename...> std::false_type IsImpl(...); inline auto constexpr is_impl = [] (auto f) constexpr { return [] (auto &&... args) constexpr { return decltype(IsImpl<decltype(f), decltype(args)&&...>(nullptr)){}; }; }; auto constexpr to_string_trait_invoke = [](auto &&a) constexpr -> decltype(to_string(a)) { return to_string(a); }; // 运行逻辑代码: if constexpr (decltype(type_traits::is_impl(to_string_trait_invoke)(v))::value) { return to_string_trait_invoke(v); }
实现原理解释
整个实现完全基于C++编译期规则实现,没有运行时开销,核心逻辑分为三层:
- 第一层是SFINAE检查核心:两个
IsImpl函数模板重载- 第一个重载为优先匹配版本:通过
std::void_t触发表达式合法性校验,尝试模拟「将Args类型的参数完美转发给F类型的可调用对象执行」的操作,如果该表达式合法(即F可以接收对应Args参数调用),该重载匹配成功,返回std::true_type。形参设置为nullptr_t是为了提升匹配优先级,传入nullptr时编译器会优先选择这个形参更精确的重载。 - 第二个重载为兜底匹配版本:使用C风格可变参数
...作为形参,匹配优先级低于第一个版本。当第一个重载因为表达式非法触发SFINAE替换失败时,编译器就会匹配到这个兜底版本,返回std::false_type。
- 第一个重载为优先匹配版本:通过
- 第二层是通用检查包装:
is_impl是一个编译期柯里化工厂lambda,传入待检查的可调用对象f后,会返回一个新的通用lambda,该lambda接收任意类型、任意数量的参数,在编译期返回对应IsImpl实例化的结果——也就是编译期常量std::true_type或std::false_type,不需要为每个待检查的函数重复编写SFINAE判断模板。 - 第三层是分支执行逻辑:
to_string_trait_invoke将to_string调用封装为统一的可调用对象,通过尾置返回类型decltype(to_string(a))提前暴露调用表达式,配合is_impl即可在编译期判断当前参数v是否支持to_string调用。最后通过C++17的if constexpr编译期分支特性,检查结果为真时保留当前分支代码,为假时直接丢弃当前分支、不产生编译错误,按顺序编写多个分支即可实现按优先级匹配调用的逻辑。
内容的提问来源于stack exchange,提问作者CutieDeng
相关产品推荐
相关产品推荐

