编译时专门针对函数指针引用以避免 -Waddress

Compile time specialize against function pointer references for avoiding -Waddress

我遇到了 GCC 的以下问题(使用 v6.4 测试它在当前主干上的行为相同)可以简化为以下简单示例:

我们进一步研究可调用对象,例如实现 operator()operator bool 的 类 以及函数指针,例如 void(*)() 和函数引用 void(&)()

以下问题可能与提前阅读相关:

我正在尝试实现一个条件调用,它会在调用可调用对象之前检查它到 bool 的转换是否是 true,然后再调用它:

/// g++-6.4 -O3 -std=c++17 -Wall

#include <functional>
#include <type_traits>

template <typename O>
void safe_invoke(O&& fn) {
  if (fn) {
    std::invoke(std::forward<O>(fn));
  }
}

void somefn() { }

int main() {
    safe_invoke(somefn);
    return 0;
}

使用 GCC 和 -Wall 时会产生警告

In instantiation of 'void safe_invoke(O&&) [with O = void (&)()]':
  warning: the compiler can assume that the address of 'fn' will always evaluate to 'true' [-Waddress]
    if (fn) {
    ^~

正如警告 GCC 使用 void(&)() 作为可调用类型 O 的正确引用类型。我处理这个警告的方法是,我想完全摆脱 bool(callable) 检查函数引用,它通过专门化具有特定特征的函数引用来永远不会为空:

/// g++-6.4 -O3 -std=c++17 -Wall -Werror
/// https://gcc.godbolt.org/z/2TCaHq

#include <functional>
#include <type_traits>

template <typename T>
struct invoke_trait {
  template <typename O>
  static void invoke(O&& fn) {
    if (fn) {
      std::invoke(std::forward<T>(fn));
    }
  }
};

template <typename Ret, typename... Args>
struct invoke_trait<Ret (*)(Args...)> {
  template <typename O>
  static void invoke(O&& fn) {
    if (fn) {
      std::invoke(std::forward<O>(fn));
    }
  }
};

template <typename Ret, typename... Args>
struct invoke_trait<Ret (&)(Args...)> {
  template <typename O>
  static void invoke(O&& fn) {
    std::invoke(std::forward<O>(fn));
  }
};

template <typename O>
void safe_invoke(O&& fn) {
  using trait_t = invoke_trait<std::decay_t<O>>;
  trait_t::invoke(std::forward<O>(fn));
}

void test() {
}

int main() {
  // No compile error as expected:
  {
    using fn_t = void (*)();
    fn_t fn = nullptr;

    safe_invoke(fn);
  }

  // the compiler can assume that the address of 'fn' will always evaluate
  // to 'true' [-Werror=address]
  {
    safe_invoke(test);
  }

  // the compiler can assume that the address of 'fn' will always evaluate
  // to 'true' [-Werror=address]
  {
    using fn_ref_t = void (&)();
    fn_ref_t fn_ref = test;

    safe_invoke(fn_ref);
  }

  return 0;
}

https://gcc.godbolt.org/z/3QAKpf

遗憾的是 GCC 在这里失败了并且总是使用 Ret (*)(Args...) 的专业化。我的代码是否存在问题,导致无法正确特化为 Ret (&)(Args...) 或者是否可以以不同的方式完成此特化? 此外,是否有其他方法可以在不显式抑制 GCC 警告的情况下阻止它(尽管这可能不是最佳解决方案)?

std::decay_t<O>

这会将函数引用转换为函数指针。

将衰减替换为删除参考和删除 CV 的组合。然后专注于 F(Args...)F(*)(Args...) 而不是 F(&)(Args...)F(*)(Args...).

template <typename Ret, typename... Args>
struct invoke_trait<Ret (*)(Args...)> {
  template <typename O>
  static void invoke(O&& fn) {
    if (fn) {
      std::invoke(std::forward<O>(fn));
    }
  }
};

template <typename Ret, typename... Args>
struct invoke_trait<Ret(Args...)> {
  template <typename O>
  static void invoke(O&& fn) {
    std::invoke(std::forward<O>(fn));
  }
};

template <typename O>
void safe_invoke(O&& fn) {
  using trait_t = invoke_trait<std::remove_cv_t<std::remove_reference_t<O>>>;
  trait_t::invoke(std::forward<O>(fn));
}

这应该行得通,最多有错别字。

你的问题出在safe_invoke函数上:

template <typename O>
void safe_invoke(O&& fn) {
  using trait_t = invoke_trait<std::decay_t<O>>;
  trait_t::invoke(std::forward<O>(fn));
}

此时O可能是一个函数引用,但std::decay_t<O>会将其衰减为函数指针。在此引用 cppreference

Otherwise, if T is a function type F or a reference thereto, the member typedef type is std::add_pointer::type.

decayed 类型因此总是选择指针特征。

也许使用

using trait_t = invoke_trait<std::remove_cv_t<O>>;

相反。