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

时间:2016-11-29 13:52:16

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

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

即。对于函数void foo(double, double),元函数将为true返回callable_t<foo, double, double>,为true返回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的编译器。

3 个答案:

答案 0 :(得分: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版本,但它不处理指向成员的指针。除此之外,我认为没关系。

答案 1 :(得分:2)

缩短使用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像以前提出的解决方案一样工作,但是在非构造类型的情况下,创建一个易于构造(我认为)指针给定类型的对象...在这个推理中我没有&# 39; 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]

答案 2 :(得分:2)

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

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>可能无法产生另一个返回值,但是文件后面可能会有另一个特化,如下所示(非常变态)摘录

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