C++14中is_callable模拟:扩展至非可继承类型的方案探讨
实现支持非可继承类型的无参数限制Callable判断Trait
需求说明
需要一个C++ trait,用来判断某个类型是否属于可调用类型(Callable),且不需要限定调用时的参数列表。相比标准库的std::is_invocable,这个trait无需对operator()的参数做任何假设,在某些场景下更实用。
之前找到的相关方案基于成员检测惯用法,但要求被测类型必须可继承,无法适配final类等非可继承类型,因此需要扩展方案。
现有尝试的局限性
最初的SFINAE实现如下:
template <typename T, typename U = void> struct is_callable : std::false_type {}; template <typename T> struct is_callable< T, std::enable_if_t< std::is_member_function_pointer<decltype(&T::operator())>::value, void>> : std::true_type {};
该方案在类仅有单个operator()时可以正常工作,但遇到operator()重载的情况就会失效——因为此时&T::operator()会产生歧义,导致SFINAE匹配失败,而原帖子的继承式方案可以处理这种重载场景。
解决方案
要同时支持重载的operator()和非可继承类型,我们可以通过构造多组调用尝试,利用SFINAE的特性来判断类型是否存在任意版本的operator(),同时无需继承被测类型。
完整实现代码
#include <type_traits> namespace detail { // 通过多组不同参数的调用尝试,匹配任意版本的operator() template<typename T> auto is_callable_impl(int) -> decltype( []<typename U>(U&& u) -> decltype(auto) { return u(); }(std::declval<T>()), []<typename U>(U&& u) -> decltype(auto) { return u(1); }(std::declval<T>()), []<typename U>(U&& u) -> decltype(auto) { return u(1, 'a'); }(std::declval<T>()), []<typename U>(U&& u) -> decltype(auto) { return u(1, 'a', 3.14); }(std::declval<T>()), std::true_type{} ); // 备选重载,当无法匹配任何operator()时触发 template<typename T> std::false_type is_callable_impl(...); } // namespace detail // 主模板:区分类类型和非类类型 template<typename T> struct is_callable : std::conditional_t< std::is_class_v<T>, decltype(detail::is_callable_impl<T>(0)), // 非类类型:直接判断是否为函数或函数指针 std::bool_constant<std::is_function_v<T> || (std::is_pointer_v<T> && std::is_function_v<std::remove_pointer_t<T>>)> > {}; // 针对各类可调用非类类型的特化 template<typename R, typename... Args> struct is_callable<R(*)(Args...)> : std::true_type {}; template<typename R, typename... Args> struct is_callable<R(Args...)> : std::true_type {}; template<typename R, typename C, typename... Args> struct is_callable<R(C::*)(Args...)> : std::true_type {}; template<typename R, typename C, typename... Args> struct is_callable<R(C::*)(Args...) const> : std::true_type {}; template<typename R, typename C, typename... Args> struct is_callable<R(C::*)(Args...) volatile> : std::true_type {}; // 便捷别名模板 template<typename T> constexpr bool is_callable_v = is_callable<T>::value;
方案说明
- 核心逻辑:在
detail::is_callable_impl中,我们通过泛型lambda尝试以不同参数列表调用被测类型的operator(),只要存在任意版本的operator()能匹配其中一组参数,就会返回std::true_type;若所有调用尝试都失败,则返回std::false_type。 - 非类类型处理:对于函数、函数指针、成员函数指针等非类可调用类型,直接通过类型特性判断或特化模板来识别。
- 兼容性:无需继承被测类型,因此可以完美支持
final类、私有构造函数类等非可继承类型,同时也能正确处理operator()重载的场景。
测试验证
// 单个operator()的普通类 struct A { void operator()() {} }; static_assert(is_callable_v<A>, "A should be callable"); // 带重载operator()的final类 struct B final { void operator()() {} int operator()(int x) { return x; } }; static_assert(is_callable_v<B>, "B should be callable"); // 函数指针 void foo() {} static_assert(is_callable_v<decltype(&foo)>, "Function pointer should be callable"); // 普通函数类型 static_assert(is_callable_v<void()>, "Function type should be callable"); // 非可调用类 struct C {}; static_assert(!is_callable_v<C>, "C should not be callable");
内容的提问来源于stack exchange,提问作者Oersted
相关产品推荐
相关产品推荐

