我正在尝试实现一个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的编译器。
答案 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;
这是一个穷人的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;
您需要记住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;
答案 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
的实施方式,并深入了解其技术挑战。