编译时专门针对函数指针引用以避免 -Waddress
Compile time specialize against function pointer references for avoiding -Waddress
我遇到了 GCC 的以下问题(使用 v6.4 测试它在当前主干上的行为相同)可以简化为以下简单示例:
我们进一步研究可调用对象,例如实现 operator()
和 operator bool
的 类 以及函数指针,例如 void(*)()
和函数引用 void(&)()
。
以下问题可能与提前阅读相关:
- Function pointer vs Function reference
我正在尝试实现一个条件调用,它会在调用可调用对象之前检查它到 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>>;
相反。
我遇到了 GCC 的以下问题(使用 v6.4 测试它在当前主干上的行为相同)可以简化为以下简单示例:
我们进一步研究可调用对象,例如实现 operator()
和 operator bool
的 类 以及函数指针,例如 void(*)()
和函数引用 void(&)()
。
以下问题可能与提前阅读相关:
- Function pointer vs Function reference
我正在尝试实现一个条件调用,它会在调用可调用对象之前检查它到 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>>;
相反。