使用元编程测试调用 f(x) 是否可行
Test if calling f(x) is possible using metaprogramming
Stroustrup 的书提供了如何回答问题的示例:"is it possible to call f(x)
if x
is of type X
"(第 28.4.4 节 "Further examples with Enable_if")。我试图重现这个例子,但出了点问题,无法理解是什么。
在我下面的代码中,有一个函数f(int)
。我希望 has_f<int>::value
的结果是 1
(true
)。实际结果是 0
(false
).
#include <type_traits>
#include <iostream>
//
// Meta if/then/else specialization
//
struct substitution_failure { };
template<typename T>
struct substitution_succeeded : std::true_type { };
template<>
struct substitution_succeeded<substitution_failure> : std::false_type { };
//
// sfinae to derive the specialization
//
template<typename T>
struct get_f_result {
private:
template<typename X>
static auto check(X const& x) -> decltype(f(x));
static substitution_failure check(...);
public:
using type = decltype(check(std::declval<T>()));
};
//
// has_f uses the derived specialization
//
template<typename T>
struct has_f : substitution_succeeded<typename get_f_result<T>::type> { };
//
// We will check if this function call be called,
// once with "char*" and once with "int".
//
int f(int i) {
std::cout << i;
return i;
}
int main() {
auto b1{has_f<char*>::value};
std::cout << "test(char*) gives: " << b1 << std::endl;
std::cout << "Just to make sure we can call f(int): ";
f(777);
std::cout << std::endl;
auto b2{has_f<int>::value};
std::cout << "test(int) gives: " << b2 << std::endl;
}
输出:
test(char*) gives: 0
Just to make sure we can call f(int): 777
test(int) gives: 0
我可以看到 2 种方法来解决您遇到的问题:
- 转发声明你的函数
f
。这是必需的,因为您在模板 get_f_result
. 中按名称显式调用函数
int f(int);
template<typename T>
struct get_f_result {
private:
template<typename X>
static auto check(X const& x) -> decltype(f(x));
static substitution_failure check(...);
public:
using type = decltype(check(std::declval<T>()));
};
- 第二种解决方案是使其更通用,即不仅适用于
f(c)
,而且适用于所有采用 int
: 的函数
//
// sfinae to derive the specialization
//
template <typename Func, Func f, typename T>
struct get_f_result {
private:
template <typename X>
static auto check(X const& x) -> decltype(f(x));
static substitution_failure check(...);
public:
using type = decltype(check(std::declval<T>()));
};
并这样称呼它:
template <typename T>
struct has_f : substitution_succeeded <typename get_f_result::type> { };
这里 f
需要在这里知道..但是,您可以再次通过转移在调用方站点提供功能的责任来使其更通用。
主要问题是您在此处对 f
进行了不合格的调用:
template<typename X>
static auto check(X const& x) -> decltype(f(x));
将找到的 f
s 将是那些在 check()
(none) 的定义点范围内的那些,以及那些通过参数相关查找找到的那些X
的关联命名空间。由于 X
是 int
,它没有关联的命名空间,而且您也找不到 f
。由于 ADL 永远不会为 int
工作,因此您的函数必须在定义 get_f_result
之前可见。只需将其向上移动即可解决该问题。
现在,您的 has_f
太复杂了。 substitution_succeeded
机器没有理由。只需要两个 check()
重载 return 你想要的类型:
template<typename T>
struct has_f {
private:
template <typename X>
static auto check(X const& x)
-> decltype(f(x), std::true_type{});
static std::false_type check(...);
public:
using type = decltype(check(std::declval<T>()));
};
现在 has_f<T>::type
已经是 true_type
或 false_type
。
当然,即使这样也过于复杂了。检查表达式是否有效是一个相当常见的操作,因此简化它会很有帮助(借用自 Yakk, similar to std::is_detected
):
namespace impl {
template <template <class...> class, class, class... >
struct can_apply : std::false_type { };
template <template <class...> class Z, class... Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type { };
};
template <template <class... > class Z, class... Ts>
using can_apply = impl::can_apply<Z, void, Ts...>;
这让你写:
template <class T>
using result_of_f = decltype(f(std::declval<T>()));
template <class T>
using has_f = can_apply<result_of_f, T>;
Stroustrup 的书提供了如何回答问题的示例:"is it possible to call f(x)
if x
is of type X
"(第 28.4.4 节 "Further examples with Enable_if")。我试图重现这个例子,但出了点问题,无法理解是什么。
在我下面的代码中,有一个函数f(int)
。我希望 has_f<int>::value
的结果是 1
(true
)。实际结果是 0
(false
).
#include <type_traits>
#include <iostream>
//
// Meta if/then/else specialization
//
struct substitution_failure { };
template<typename T>
struct substitution_succeeded : std::true_type { };
template<>
struct substitution_succeeded<substitution_failure> : std::false_type { };
//
// sfinae to derive the specialization
//
template<typename T>
struct get_f_result {
private:
template<typename X>
static auto check(X const& x) -> decltype(f(x));
static substitution_failure check(...);
public:
using type = decltype(check(std::declval<T>()));
};
//
// has_f uses the derived specialization
//
template<typename T>
struct has_f : substitution_succeeded<typename get_f_result<T>::type> { };
//
// We will check if this function call be called,
// once with "char*" and once with "int".
//
int f(int i) {
std::cout << i;
return i;
}
int main() {
auto b1{has_f<char*>::value};
std::cout << "test(char*) gives: " << b1 << std::endl;
std::cout << "Just to make sure we can call f(int): ";
f(777);
std::cout << std::endl;
auto b2{has_f<int>::value};
std::cout << "test(int) gives: " << b2 << std::endl;
}
输出:
test(char*) gives: 0
Just to make sure we can call f(int): 777
test(int) gives: 0
我可以看到 2 种方法来解决您遇到的问题:
- 转发声明你的函数
f
。这是必需的,因为您在模板get_f_result
. 中按名称显式调用函数
int f(int);
template<typename T>
struct get_f_result {
private:
template<typename X>
static auto check(X const& x) -> decltype(f(x));
static substitution_failure check(...);
public:
using type = decltype(check(std::declval<T>()));
};
- 第二种解决方案是使其更通用,即不仅适用于
f(c)
,而且适用于所有采用int
: 的函数
// // sfinae to derive the specialization // template <typename Func, Func f, typename T> struct get_f_result { private: template <typename X> static auto check(X const& x) -> decltype(f(x)); static substitution_failure check(...); public: using type = decltype(check(std::declval<T>())); };
并这样称呼它:
template <typename T> struct has_f : substitution_succeeded <typename get_f_result::type> { };
这里 f
需要在这里知道..但是,您可以再次通过转移在调用方站点提供功能的责任来使其更通用。
主要问题是您在此处对 f
进行了不合格的调用:
template<typename X>
static auto check(X const& x) -> decltype(f(x));
将找到的 f
s 将是那些在 check()
(none) 的定义点范围内的那些,以及那些通过参数相关查找找到的那些X
的关联命名空间。由于 X
是 int
,它没有关联的命名空间,而且您也找不到 f
。由于 ADL 永远不会为 int
工作,因此您的函数必须在定义 get_f_result
之前可见。只需将其向上移动即可解决该问题。
现在,您的 has_f
太复杂了。 substitution_succeeded
机器没有理由。只需要两个 check()
重载 return 你想要的类型:
template<typename T>
struct has_f {
private:
template <typename X>
static auto check(X const& x)
-> decltype(f(x), std::true_type{});
static std::false_type check(...);
public:
using type = decltype(check(std::declval<T>()));
};
现在 has_f<T>::type
已经是 true_type
或 false_type
。
当然,即使这样也过于复杂了。检查表达式是否有效是一个相当常见的操作,因此简化它会很有帮助(借用自 Yakk, similar to std::is_detected
):
namespace impl {
template <template <class...> class, class, class... >
struct can_apply : std::false_type { };
template <template <class...> class Z, class... Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type { };
};
template <template <class... > class Z, class... Ts>
using can_apply = impl::can_apply<Z, void, Ts...>;
这让你写:
template <class T>
using result_of_f = decltype(f(std::declval<T>()));
template <class T>
using has_f = can_apply<result_of_f, T>;