C++ 元函数,它确定一个类型是否可以为提供的参数调用

C++ meta function that determines if a type is callable for supplied arguments

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

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

我的尝试如下,但是对于 return 除了 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 的编译器。

以下是我的处理方法:

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,但它不处理指向成员的指针。除此之外,我觉得还好。

缩短使用 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<T(Args...)>(), void())).

技巧来完成它

编辑:

根据有关该解决方案可能存在的缺点的评论详细说明主题。 std::result_of_t<T(Args...)> 类型不需要配备默认的非参数构造函数,因此 sfinae 可能会导致 callable_v 函数产生这种类型的假阴性结果。在评论中,我针对该问题提出了一种解决方法,但它并没有真正解决问题或实际上产生了一个新问题:

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

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

来解决
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]

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

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<Func, int> 可能无法产生另一个 return 值,但稍后在文件中可能会有另一个特化,如下所示 (很变态) 片断

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 的实现方式并深入了解它技术挑战。