C++编译时 predicate ,用于测试F类型的可调用对象是否可以用T类型的参数调用

oknwwptz  于 2023-10-21  发布在  其他
关注(0)|答案(4)|浏览(124)

我想创建一个编译类型的函数,给定任何可调用对象f(函数,lambda表达式,函数对象,...)和T类型,如果f可以用T类型的参数调用,则计算结果为true,如果不能,则为false。
范例:

void f1(int) { ... }
void f2(const std::string&) { ... }

assert( is_callable_with<int>(f1));
assert(!is_callable_with<int>(f2));

我认为巧妙地使用SFINAE规则可以实现这一点。可能是这样的:

template<typename T, typename F>
constexpr bool is_callable_with(F&&, typename std::result_of<F(T)>::type* = nullptr) {
  return true;
}

template<typename T, typename F>
constexpr bool is_callable_with(F&&) {
  return false;
}

但这不起作用,因为如果F可以用T调用,则两个重载都参与重载解析,并且存在二义性。我想重写它,所以在积极的情况下,第一个重载将被重载决议选中,而不是第二个。我甚至不确定我是否在正确的轨道上。

lf5gs5x2

lf5gs5x21#

Paul's answer的变体,但遵循标准SFINAE测试模式。同样是一个带有任意参数类型A...的泛型trait:

struct can_call_test
{
    template<typename F, typename... A>
    static decltype(std::declval<F>()(std::declval<A>()...), std::true_type())
    f(int);

    template<typename F, typename... A>
    static std::false_type
    f(...);
};

template<typename F, typename... A>
using can_call = decltype(can_call_test::f<F, A...>(0));

然后按照您的要求执行constexpr函数:

template<typename T, typename F>
constexpr bool is_callable_with(F&&) { return can_call<F, T>{}; }

检查live example
这将适用于函数,lambda表达式或具有任意数量参数的函数对象,但对于(指向)成员函数,您必须使用std::result_of<F(A...)>

更新

下面,can_call具有std::result_of的漂亮的“函数签名”语法:

template<typename F, typename... A>
struct can_call : decltype(can_call_test::f<F, A...>(0)) { };

template<typename F, typename... A>
struct can_call <F(A...)> : can_call <F, A...> { };

被这样利用

template<typename... A, typename F>
constexpr can_call<F, A...>
is_callable_with(F&&) { return can_call<F(A...)>{}; }

在这里,我还使is_callable_with变元(我不明白为什么它应该被限制为一个参数),并返回与can_call相同的类型,而不是bool(感谢Yakk)。
再一次,live example here

vd2z7a6w

vd2z7a6w2#

我会先创建一个类型trait:

template<class X = void>
struct holder
{
    typedef void type;
};

template<class F, class T, class X = void>
struct is_callable_with_trait
: std::false_type
{};

template<class F, class T>
struct is_callable_with_trait<F, T, typename holder<
    decltype(std::declval<F>()(std::declval<T>()))
>::type>
: std::true_type
{};

如果你愿意,你可以把它变成一个函数:

template<typename T, typename F>
constexpr bool is_callable_with(F&&) 
{
    return is_callable_with_trait<F&&, T>::value;
}
jhdbpxl9

jhdbpxl93#

template<class F, class T, class = void>
struct is_callable_with_impl : std::false_type {};

template<class F, class T>
struct is_callable_with_impl<F,T,
     typename std::conditional< 
              true,
              void,
              decltype( std::declval<F>() (std::declval<T>()) ) >::type
      > : std::true_type {};

template<class T, class F>
constexpr bool is_callable_with(F &&) 
{ 
     return is_callable_with_impl< F, T >::value; 
}

它基本上与Paul发布的解决方案相同,我只是更喜欢使用conditional<true, void, decltype( ... ) >而不是holder类来避免命名空间污染。

hts6caw3

hts6caw34#

在C++ 17中,现在是std::is_invocable

static_assert( std::is_invocable_v<decltype(f1), int> );
static_assert( !std::is_invocable_v<decltype(f2), int> );

我花了一段时间才找到,可以为某人保存一些时间。

相关问题