You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

方案说明

  1. 核心逻辑:在detail::is_callable_impl中,我们通过泛型lambda尝试以不同参数列表调用被测类型的operator(),只要存在任意版本的operator()能匹配其中一组参数,就会返回std::true_type;若所有调用尝试都失败,则返回std::false_type。
  2. 非类类型处理:对于函数、函数指针、成员函数指针等非类可调用类型,直接通过类型特性判断或特化模板来识别。
  3. 兼容性:无需继承被测类型,因此可以完美支持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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 16:34:55