简体   繁体   中英

Compile time specialize against function pointer references for avoiding -Waddress

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)?

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM