背景
什么时候想要判断类里是否有某个函数?似乎使用场景不多。这里举个例子:
日志打印,将任意类型的对象转换为字符串打印出来。可以检查该对象是否有ToString
方法,有的话则调用该方法获取返回值进行打印。
那么这里就以判断类是否有
ToString
函数为例,先从简单的开始:判断类是否有 ToString() 方法,不指定参数和返回值
#include <string> #include <iostream> #include <type_traits> // Helper to detect if a type has a ToString method with no parameters template<typename T> class HasToStringFunction { private: // This overload will be chosen if T has a ToString method with no parameters template<typename U> static auto Check() -> decltype(std::declval<U>().ToString(), std::true_type()); // This overload will be chosen if T does not have a ToString method with no parameters template<typename U> static std::false_type Check(...); public: // The value will be true if the first overload is chosen, false otherwise static constexpr bool value = std::is_same<decltype(Check<T>()), std::true_type>::value; };
代码解释
- typename:告诉编译器,后面的名字是类型,而不是变量,详细的解释参考这篇文章
- decltype:编译器类型推导,获取变量或者表达式的类型
- std::declval:返回某个类型的右值引用 T&&
- std::true_type, std::false_type:在数值计算中,有 true/false 值表示真假,在类型计算中 true/false 都是 bool,需要在类型的世界中有表示真假的类型,就是 true_type 和 false_type 了
- std::is_same:判断两个类型是否相同
- 在编译期,代码会判断类型 T 能够匹配到哪个 Check,如果 T 类型有 ToString 方法,则匹配到第一个 Check 函数,返回 std::true_type,接着 value 会被设置为 true。
测试
// Online editor: https://godbolt.org/z/3vE9WqWaY // 函数签名:无参数,无返回值 class ToStringNoArgsReturnVoid { public: void ToString() {}; }; // 函数签名:有参数,无返回值,可与上面的 ToStringNoArgsReturnVoid 对比验收参数类型检查的结果 class ToStringIntArgsReturnVoid { public: void ToString(int i) {}; }; // 函数签名:无参数,有返回值,可与上面的 ToStringNoArgsReturnVoid 对比验收返回值类型的检查结果 class ToStringNoArgsReturnInt { public: int ToString() { return 1; }; }; // 无 ToString 方法 class NoToString { }; // 访问权限为 private 的 ToString 方法,可对比得出访问权限是否影响函数的存在性检测 class PrivateToStringNoArgsReturnVoid { private: void ToString() {}; }; int main() { if (HasToStringFunction<ToStringNoArgsReturnVoid>::value) { std::cout << "ToStringNoArgsReturnVoid class has ToString()" << std::endl; } if (!HasToStringFunction<ToStringIntArgsReturnVoid>::value) { std::cout << "ToStringIntArgsReturnVoid class has no ToString()" << std::endl; } if (HasToStringFunction<ToStringNoArgsReturnInt>::value) { std::cout << "ToStringNoArgsReturnInt class has ToString() " << std::endl; } if (!HasToStringFunction<NoToString>::value) { std::cout << "NoToString class has no ToString() " << std::endl; } if (!HasToStringFunction<PrivateToStringNoArgsReturnVoid>::value) { std::cout << "PrivateToStringNoArgsReturnVoid class has no ToString()" << std::endl; } } /** output: ToStringNoArgsReturnVoid class has ToString() ToStringIntArgsReturnVoid class has no ToString() ToStringNoArgsReturnInt class has ToString() NoToString class has no ToString() PrivateToStringNoArgsReturnVoid class has no ToString() **/
判断类是否有 ToString() 方法,可指定参数,不指定返回值
// Helper to detect if a type has a ToString method with any parameters template<typename T, typename... Args> class HasToString { private: // This overload will be chosen if T has a ToString method with the given parameters template<typename U> static auto Check() -> decltype(std::declval<U>().ToString(std::declval<Args>()...), std::true_type()); // This overload will be chosen if T does not have a ToString method with the given parameters template<typename> static std::false_type Check(...); public: // The value will be true if the first overload is chosen, false otherwise static constexpr bool value = std::is_same<decltype(Check<T>()), std::true_type>::value; };
代码解释
这里只需要了解(记住)传入可变参数的写法。模板参数会传递到
ToString
中参与匹配,达到匹配函数参数的目的。测试
新增对带参数的 ToString 方法的判断,执行结果符合预期:
if (HasToString<ToStringIntArgsReturnVoid, int>::value) { std::cout << "ToStringIntArgsReturnVoid has ToString method with one int argument" << '\n'; } else { std::cout << "ToStringIntArgsReturnVoid does not have ToString method" << '\n'; } if (HasToString<ToStringIntArgsReturnVoid>::value) { std::cout << "ToStringIntArgsReturnVoid has ToString method" << '\n'; } else { std::cout << "ToStringIntArgsReturnVoid does not have ToString method without args" << '\n'; } /* output: ToStringIntArgsReturnVoid has ToString method with one int argument ToStringIntArgsReturnVoid does not have ToString method without args */
判断类是否有 ToString() 方法,可指定参数和返回值
template<typename T, typename RetType, typename... Args> class HasToStringWithReturnType { template<typename U> static auto Check() -> std::enable_if_t<std::is_same<RetType, decltype(std::declval<U>().ToString(std::declval<Args>()...))>::value, std::true_type>; template<typename U> static std::false_type Check(...); public: static constexpr bool value = std::is_same<decltype(Check<T>()), std::true_type>::value; };
代码解释
- std::enable_if_t:如果
std::is_same<RetType, decltype(std::declval<U>().ToString(std::declval<Args>()...))>::value
是 true,则返回 std::true_type 类型的返回值,否则返回 void 类型的返回值。
- 在编译期,能匹配到参数和返回值都是模板中指定的,则会命中第一个 Check,返回一个 std::true_type 的值,在 value 的赋值过程中通过 is_same 判断,设置为 true
测试
新增对有返回值的 ToString 方法的判断,执行结果符合预期:
if (HasToStringWithReturnType<ToStringNoArgsReturnInt, void>::value) { std::cout << "ToStringNoArgsReturnInt have ToString method return void" << '\n'; } else { std::cout << "ToStringNoArgsReturnInt does not have ToString method return void" << '\n'; } if (HasToStringWithReturnType<ToStringNoArgsReturnInt, int>::value) { std::cout << "ToStringNoArgsReturnInt have ToString method return int" << '\n'; } else { std::cout << "ToStringNoArgsReturnInt does not have ToString method return int" << '\n'; } /* output: ToStringNoArgsReturnInt does not have ToString method return void ToStringNoArgsReturnInt have ToString method return int */