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

如何用constexpr检查类型是否可调用(含泛型Lambda、任意参数场景)

Alright, let's tackle these two callable type checking requirements step by step—they're a bit tricky, especially with generic lambdas in the mix, so let's break them down clearly.

1. 需求2:实现constexpr工具检测类型F是否为可调用对象

First, let's look at the is_callable template you mentioned:

template<class F, class... Args> struct is_callable {
    template<class U> static auto test(U* p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
    template<class> static auto test(...) -> std::false_type;
    static constexpr bool value = decltype(test<F>(nullptr))::value;
};

This works perfectly for non-template callables, but it has a critical limitation: you have to explicitly specify the argument types Args.... That's useless for generic lambdas—their operator() is a template with no fixed parameter types, so we can't predefine args to test with.

To build a constexpr tool that detects any callable type (including generic lambdas, function pointers, member function pointers, and function objects) without needing specific args, we can use SFINAE to check for the presence of a callable operator() (even template ones) or match common callable type signatures directly. Here's a C++17-compatible implementation:

#include <type_traits>

// Base case: assume not callable
template<typename F, typename = void>
struct is_callable : std::false_type {};

// Specializations for function pointers, plain functions, member functions
template<typename Ret, typename... Args>
struct is_callable<Ret(Args...)> : std::true_type {};

template<typename Ret, typename... Args>
struct is_callable<Ret(*)(Args...)> : std::true_type {};

template<typename Ret, typename Class, typename... Args>
struct is_callable<Ret(Class::*)(Args...)> : std::true_type {};

template<typename Ret, typename Class, typename... Args>
struct is_callable<Ret(Class::*)(Args...) const> : std::true_type {};

// Specialization for types with an operator() (including generic lambdas)
template<typename F>
struct is_callable<F, std::void_t<decltype(&F::operator())>> : std::true_type {};

// Helper variable for cleaner usage
template<typename F>
constexpr bool is_callable_v = is_callable<F>::value;

The magic here is:

  • std::void_t lets us use SFINAE to check if &F::operator() is a valid expression (even for generic lambdas, whose templated operator() still produces a valid pointer-to-member-template)
  • We cover all common callable type signatures to avoid missing edge cases

Test it with a generic lambda to confirm:

int main() {
    auto generic_lambda = [](auto&&...) {};
    static_assert(is_callable_v<decltype(generic_lambda)>); // Compiles successfully
    static_assert(is_callable_v<int(*)(int)>); // Compiles successfully
    static_assert(!is_callable_v<int>); // Compiles successfully
}
2. 需求1:检查类型T是否可被任意参数调用

This is a stricter requirement: we need to verify that T can be called with any number and type of arguments. Examples of such types include:

  • Generic lambdas with auto&&... parameters
  • Custom function objects with a templated operator() accepting any args
  • std::function<void(...)> (C++11's variadic function wrapper)

C++20 Solution (Clean & Precise)

C++20 Concepts make this trivial to express clearly:

#include <concepts>

template<typename T>
concept callable_with_any_args = requires(T t, auto&&... args) {
    t(std::forward<decltype(args)>(args)...);
};

Usage example:

auto any_args_lambda = [](auto&&...) {};
static_assert(callable_with_any_args<decltype(any_args_lambda)>); // Passes

std::function<void(...)> func;
static_assert(callable_with_any_args<decltype(func)>); // Passes

auto fixed_args_lambda = [](int) {};
static_assert(!callable_with_any_args<decltype(fixed_args_lambda)>); // Passes

C++17 & Earlier Solution

If you can't use C++20, we can approximate this by checking if T can be called with increasing numbers of distinct argument types. It's not 100% theoretically perfect (there could be types that only accept up to N args), but it works for real-world scenarios:

#include <type_traits>

// Base implementation
template<typename T, typename... Args>
struct is_callable_with_any_args_impl : std::false_type {};

// Recursive case: if T accepts Args..., check with more args
template<typename T, typename Arg1, typename... Args>
struct is_callable_with_any_args_impl<T, Arg1, Args...> : 
    std::conditional_t<
        std::is_invocable_v<T, Arg1, Args...>,
        is_callable_with_any_args_impl<T, Args..., int>, // Add a new unique type
        std::false_type
    > {};

// Termination case: check if T accepts 0 args
template<typename T>
struct is_callable_with_any_args_impl<T> : std::is_invocable_v<T> {};

// Helper variable: check 0, 1, 2, 3, 4 args (adjust as needed)
template<typename T>
constexpr bool is_callable_with_any_args_v = 
    is_callable_with_any_args_impl<T>::value &&
    is_callable_with_any_args_impl<T, int>::value &&
    is_callable_with_any_args_impl<T, int, double>::value &&
    is_callable_with_any_args_impl<T, int, double, char>::value &&
    is_callable_with_any_args_impl<T, int, double, char, void*>::value;

Test it the same way:

auto any_args_lambda = [](auto&&...) {};
static_assert(is_callable_with_any_args_v<decltype(any_args_lambda)>); // Passes

auto fixed_args_lambda = [](int) {};
static_assert(!is_callable_with_any_args_v<decltype(fixed_args_lambda)>); // Passes

内容的提问来源于stack exchange,提问作者Andreas Loanjoe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:17:59