【问题标题】:C++ meta function that determines if a type is callable for supplied argumentsC++ 元函数,用于确定类型是否可用于提供的参数
【发布时间】:2017-04-13 13:14:15
【问题描述】:

我正在尝试实现一个 C++ 模板元函数,该函数确定一个类型是否可以从方法输入参数中调用。

即对于函数void foo(double, double),元函数将返回true for callable_t<foo, double, double>true for callable_t<foo, int, int>(由于编译器执行隐式转换)和false 对于其他任何事情,例如错误数量的参数callable_t<foo, double> .

我的尝试如下,但是对于返回 void 以外的任何函数的任何函数都失败了,我似乎无法修复它。

我是模板重新编程的新手,因此我们将不胜感激。

#include <iostream>
#include <type_traits>
#include <utility>
#include <functional>

namespace impl
{

template <typename...>
struct callable_args
{
};

template <class F, class Args, class = void>
struct callable : std::false_type
{
};

template <class F, class... Args>
struct callable<F, callable_args<Args...>, std::result_of_t<F(Args...)>> : std::true_type
{
};

}

template <class F, class... Args>
struct callable : impl::callable<F, impl::callable_args<Args...>>
{
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, Args...>::value;


int main()
{
    {
        using Func = std::function<void()>;
        auto result = callable_v<Func>;
        std::cout << "test 1 (should be 1) = " << result << std::endl;
    }

    {
        using Func = std::function<void(int)>;
        auto result = callable_v<Func, int>;
        std::cout << "test 2 (should be 1) = " << result << std::endl;
    }

    {
        using Func = std::function<int(int)>;
        auto result = callable_v<Func, int>;
        std::cout << "test 3 (should be 1) = " << result << std::endl;
    }

    std::getchar();

    return EXIT_SUCCESS;
}

我正在使用支持 C++ 14 的编译器。

【问题讨论】:

  • callable_t&lt;foo, float, float&gt; 呢?
  • @NathanOliver,编译器推断的任何参数都是可调用的(尽管带有警告)应该是有效的,所以如果 foo 被定义为 foo(int, int) 或 @,callable_t&lt;foo, float, float&gt; 就可以了987654335@ 或 foo(float, float) 但不是 foo(custom_type, custom_type) 其中 custom_type 不能被隐式转换。
  • @Marco A. 遗憾的是:-(
  • @keith (edited msg) 如果你的编译器也支持一些 C++17 特性,我推荐 is_callable。否则here's a hackish fix for your code。请注意,此代码有许多缺陷,例如完全避免 is_convertible 转换和模板参数格式正确。
  • @Marco A,谢谢,我已经尝试过了,但是在这里查看std::void_t,我的 c++ 14 编译器似乎对简单的 void_t 定义有问题。在链接中使用建议的 void_t 实现使您的示例可以在我的编译器上运行!为了学习,我很想听到更多关于我的代码中的缺陷的信息。我正处于进入 TMP 之旅的开始阶段。

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


【解决方案1】:

缩短使用std::result_of 来做你想做的事情可能如下所示:

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

您需要记住result_of 返回的类型始终是您按类型传递给此特征的函数的结果类型。为了让您的 sfinae 工作,您需要一种方法来将这种类型更改为在每种可能的情况下都无效。您可以通过使用 decltype (decltype(std::result_of_t&lt;T(Args...)&gt;(), void())) 的技巧来完成它。

编辑:

详细说明来自 cmets 的关于该解决方案可能存在的缺点的线程。 std::result_of_t&lt;T(Args...)&gt; 类型不需要配备默认的非参数构造函数,因此对于导致这种类型的函数,sfinae 可能会导致 callable_v 的假阴性结果。在 cmets 中,我针对该问题提出了一种解决方法,但并未真正解决问题或实际生成新问题:

decltype(std::declval<std::result_of_t<T(Args...)>*>(), void())

此代码的目的是使 sfinae 像以前提出的解决方案一样工作,但在不可构造类型的情况下,创建一个易于构造的(我认为)指向给定类型的指针对象......在这个推理中,我没有t 考虑到无法创建指针的类型,例如参考。这又可以通过使用一些额外的包装类来解决:

decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void())

或者通过衰减结果类型:

decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void())

但我认为这可能不值得,也许使用 void_t 实际上是一个更直接的解决方案:

template <class...>
struct voider {
    using type = void;
};

template <class... Args>
using void_t = typename voider<Args...>::type;

template <class T, class, class... Args>
struct callable: std::false_type {
};

template <class T, class... Args>
struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type {
};

template <class F, class... Args>
constexpr auto callable_v = callable<F, void, Args...>::value;

[live demo]

【讨论】:

  • 我喜欢这个。我确实尝试过类似但失败了。很好的例子(我支持你):-) 在你的例子中,我可以使用 void_t 而不是你的 decltype 技巧,但是学习 decltype 技巧很有用 - 谢谢!
  • @keith 我更喜欢decltype 而不是void_t,因为它允许您进行自定义调整...例如,如果您愿意,您可以将任何类型更改为 int - 您不会注定要更改一切都作废......更重要的是,当您在 c++11 中获得 decltype 开箱即用时,您不需要实现任何东西
  • 在任何情况下这都行不通吗?它非常简单和优雅。这里有一个类似的版本:talesofcpp.fusionfenix.com/post-11/true-story-call-me-maybe
  • @keith 好吧,我能想到一个案例——如果没有函数结果类型的默认构造函数,但可以通过使用 decltype(std::declval&lt;std::result_of_t&lt;T(Args...)&gt;*&gt;(), void()) 而不是之前的 @ 轻松修复此用例987654338@ 声明,或者您可以按照您的建议使用void_t。还有其他情况吗?也许,但我现在想不出……
  • /叹息。 std::declval&lt;std::result_of_t&lt;...&gt;&gt;() 足以解决不可施工性问题,但无论如何void_t 是优越的。 , void() 有一些令人讨厌的微妙之处 - 它仍然会进行重载解析,并且可能会触发不需要的模板实例化。
【解决方案2】:

以下是我的处理方法:

namespace detail {

template<typename Func, typename...Params> static auto helper(int) ->
    decltype((void)std::declval<Func>()(std::declval<Params>()...), std::true_type{});

template<typename Func, typename...Params> static std::false_type helper(...);

}

template<typename Func, typename... Params> struct callable:
    decltype(detail::helper<Func, Params...>(0)){};

template <class F, class... Args> constexpr auto callable_v =
    callable<F, Args...>::value;

demo

它是 C++1z 的 is_callable 的穷人版本,但它不处理指向成员的指针。除此之外,我觉得还不错。

【讨论】:

  • 谢谢,我给了你一个赞成票,因为它非常有趣并且比我的代码简单得多。它并不能帮助我理解如何修复我的代码:-)
【解决方案3】:

您的原始代码的问题在于您在不可推导的上下文中使用参数包

namespace impl
{

  template <class F, class... Args>
  struct callable : std::false_type
  {
  };

  template <class F, class... Args>
  struct callable<F, std::result_of_t<F(Args...)>> : std::true_type
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  {
  };

}

在源代码解析的这一点上,std::result_of_t&lt;Func, int&gt; 可能无法产生另一个返回值,但文件后面可能会有另一个专门化,如下所示(非常变态 ) sn-p

namespace std {

    template <>
    struct result_of<Func(int)> {
        using type = double;
    };
}

因此,您的编译器应该同时检查所有这些,然后才能选择正确的。

这也是为什么像

这样的变通办法
template< class... > using void_t = void;

namespace impl
{

  template <typename...>
  struct callable_args
  {
  };

  template <class F, class Args, class = void>
  struct callable : std::false_type
  {
  };

  template <class F, class... Args>
  struct callable<F, callable_args<Args...>, void_t<std::result_of_t<F(Args...)>>> : std::true_type
  {
  };

}

在您的情况下工作:它们帮助编译器将依赖类型解析为始终解析为 void 的东西。请记住,上面的代码是一种解决方法,您应该使用is_callable (C++17) 或研究is_callable 的实现方式并深入了解其技术挑战。

【讨论】:

  • 等等,你确定非推导上下文是这里的根本原因,并且在模板专业化模式匹配中不会失败吗?我不相信可以按照您的建议阅读问题...
  • 感谢您提供的信息,感谢您的支持。我将四处寻找编译器供应商对 is_callable 的实现,以了解它是如何工作的。
猜你喜欢
  • 2011-07-03
  • 1970-01-01
  • 1970-01-01
  • 2022-09-28
  • 2014-06-21
  • 2011-08-06
  • 2019-10-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多