Reputation: 1573
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)?
Upvotes: 3
Views: 174
Reputation: 9825
Your problem lies in the safe_invoke
function:
template <typename O>
void safe_invoke(O&& fn) {
using trait_t = invoke_trait<std::decay_t<O>>;
trait_t::invoke(std::forward<O>(fn));
}
At this point O
might be a function reference, but std::decay_t<O>
will decay it to a function pointer. To quote cppreference on this:
Otherwise, if T is a function type F or a reference thereto, the member typedef type is std::add_pointer::type.
The decayed type will therefore always choose the pointer trait.
Maybe use
using trait_t = invoke_trait<std::remove_cv_t<O>>;
instead.
Upvotes: 3
Reputation: 275310
std::decay_t<O>
This converts function references to function pointers.
Replace decay with a combination of remove reference and remove CV. Then specialize on F(Args...)
and F(*)(Args...)
instead of F(&)(Args...)
and 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));
}
That should work, up to typos.
Upvotes: 3