I'm encountering the following issue with GCC (tested with v6.4 it's the same behavior on the current trunk) which can be reduced to the following minimalistic example:
Further we look at callable objects such as classes that implement operator()
and operator bool
and function pointers such as void(*)()
and function references void(&)()
.
Following questions might be relevant to be read in advance:
I'm trying to implement a conditional invoke which checks before a callable is invoked whether its conversion to bool
is true
before invoking it:
/// 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;
}
Will yield a warning when using GCC and -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) {
^~
As indicated by the warning GCC uses void(&)()
as the correct reference type of the callable type O
. My approach in dealing with this warning was that I want to get completely rid of the bool(callable)
check for a function references which can never be null by specializing those with a specific trait:
/// 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
Sadly GCC fails here and always uses the specialization for Ret (*)(Args...)
. Is there an issue with my code which prevents the correct specialization to Ret (&)(Args...)
or can this specialization be done differently?
Additionally is there a different way to prevent the GCC warning without suppressing it explicitly (although this might not be the optimal solution)?