【问题标题】:C++ compile-time predicate to test if a callable object of type F can be called with an argument of type TC++ 编译时谓词,用于测试是否可以使用 T 类型的参数调用 F 类型的可调用对象
【发布时间】:2014-05-17 21:51:15
【问题描述】:

我想创建一个编译类型函数,给定任何可调用对象f(函数、lambda 表达式、函数对象...)和类型T,如果f,则计算结果为真可以使用 T 类型的参数调用,如果不能调用,则为 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 调用,则两个重载都参与重载解析,并且存在歧义。我想重写它,所以在积极的情况下,第一个重载将被重载决议选择而不是第二个重载。不过,我不确定我是否走在正确的轨道上。

【问题讨论】:

  • 好吧,你必须让一个重载成为参数的更差匹配。例如,您可以在第二个重载中添加一个省略号,并通过添加一个附加参数 nullptr(调用 is_callable_with&lt;T&gt;(f, nullptr))来添加一个选择这两个重载之一的函数。
  • 我是唯一一个在计算开始在他的 C++ 项目中使用 Concepts 的日子的人吗? :P

标签: c++ templates c++11 template-meta-programming


【解决方案1】:

我会先做一个类型特征:

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;
}

【讨论】:

  • 你打错了,第二个false_type应该是true_type
  • 我通常打电话给holdertype_sink,因为这是一个类型流入和流出的地方。
【解决方案2】:
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&lt;true, void, decltype( ... ) &gt; 而不是holder 类以避免命名空间污染。

【讨论】:

  • 那为什么不用decltype( std::declval&lt;F&gt;() (std::declval&lt;T&gt;()), void() )呢?
【解决方案3】:

Paul's answer 的变体,但遵循标准 SFINAE 测试模式。同样是具有任意参数类型的通用特征A...

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&lt;F(A...)&gt;


更新

在下面,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

【讨论】:

  • 既然可以返回std::true_typestd::false_type,为什么还要返回bool?如给定非constexpr 函数指针所写,返回值是编译时间常数吗?
  • @Yakk 当然,这样更好。我只想保留与问题代码相同的签名。
  • 甜,这行得通!几个我不知道的好技巧。谢谢!
猜你喜欢
  • 2022-01-01
  • 2018-12-26
  • 2011-12-15
  • 2022-11-16
  • 1970-01-01
  • 1970-01-01
  • 2014-05-03
  • 2013-05-04
  • 1970-01-01
相关资源
最近更新 更多