使用元编程测试调用 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 种方法来解决您遇到的问题:

  1. 转发声明你的函数f。这是必需的,因为您在模板 get_f_result.
  2. 中按名称显式调用函数

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>())); };

  1. 第二种解决方案是使其更通用,即不仅适用于 f(c),而且适用于所有采用 int:
  2. 的函数
//
// 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));

将找到的 fs 将是那些在 check() (none) 的定义点范围内的那些,以及那些通过参数相关查找找到的那些X 的关联命名空间。由于 Xint,它没有关联的命名空间,而且您也找不到 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_typefalse_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>;