Naios / function2

Improved and configurable drop-in replacement to std::function that supports move only types, multiple overloads and more
http://naios.github.io/function2
Boost Software License 1.0
539 stars 47 forks source link

Compile time bug with noexcept specifier #50

Closed MBkkt closed 2 years ago

MBkkt commented 2 years ago

@Naios

Cannot use noexcept specifier. Looks like a compile time bug. I think something should be sfinae but its not


Commit Hash

last, 035bec21533f484c1b5dd8dd0380109ea431f6a1

Expected Behavior

Should compile

Actual Behavior

Don't compile

Steps to Reproduce

See godbolt

Your Environment

Naios commented 2 years ago

Your issue is missing a minimalistic reproducible example: I cannot guess from vague chosen identifiers like "Bruh" or "Kek" what is the behaviour you are expecting from the library and whats not working as intended. Provide an example with no indirections (over unique_ptr and constructors) please.

Note that noexcept in function definitions only works on C++17 and above (due to limitations of earlier standards).

MBkkt commented 2 years ago

@Naios I think its simple, and you are nitpicking, but https://godbolt.org/z/bfYW539YW

MBkkt commented 2 years ago

For some reason GCC starts with 10 or 11 (forgot) first try to substitute function ctor instead of move ctor

MBkkt commented 2 years ago

Make it better and permanent link

MBkkt commented 2 years ago

Example of the real usage: Some object that will be stored as ptr to virtual base class, also contains CallbackGuard that contains noexcept functor

Naios commented 2 years ago

@MBkkt I'm not nitpicking. The naming of identifiers is not truly relevant in this example, the indirection however is. If you remove the additional indirections, your problem is instantly visible:

#include "https://raw.githubusercontent.com/Naios/function2/master/include/function2/function2.hpp"

struct FunctorStorage {
  explicit FunctorStorage(fu2::unique_function<void() noexcept>) noexcept {}
  explicit FunctorStorage(FunctorStorage&&) noexcept {}
};

int main() {
    FunctorStorage* fn = nullptr;
    FunctorStorage impl{std::move(*fn)};
    return 0;
}

It seems like there is something wrong with the SFINAE guarding the constructor which works without noexcept but not with noexcept specified.

Until this gets fixed you can use a tag to differenciate between your constructors:

#include "https://raw.githubusercontent.com/Naios/function2/master/include/function2/function2.hpp"

struct Tag { };

struct FunctorStorage {
  explicit FunctorStorage(Tag, fu2::unique_function<void() noexcept>) noexcept {}
  explicit FunctorStorage(FunctorStorage&&) noexcept {}
};

struct Base {};

struct Impl : Base {
  explicit Impl(FunctorStorage&& s) noexcept : storage{std::move(s)} {}
  FunctorStorage storage;
};

int main() {
    Impl impl{FunctorStorage{Tag{}, []() noexcept { }}};
    return 0;
}
MBkkt commented 2 years ago

@Naios Btw fix is easy:

template<typename Trait, typename T, bool Callable>
struct is_noexcept_correct_lazy {
  static constexpr bool value = invocation::is_noexcept_correct<
                        Trait::is_noexcept::value,
                        typename Trait::template callable<T>,
                        typename Trait::arguments>::value;
};

template<typename Trait, typename T>
struct is_noexcept_correct_lazy<Trait, T, false> {
  static constexpr bool value = false;
};

/// Deduces to a true_type if the type T provides the given signature and the
/// signature is noexcept correct callable.
template <typename T, typename Signature,
          typename Trait =
              type_erasure::invocation_table::function_trait<Signature>>
struct accepts_one
    : std::integral_constant<
          bool, is_noexcept_correct_lazy<Trait, T,
           invocation::can_invoke<typename Trait::template callable<T>,
                                  typename Trait::arguments>::value
                                  >::value> {};
MBkkt commented 2 years ago

Or

template <typename T, typename Signature,
          typename Trait =
              type_erasure::invocation_table::function_trait<Signature>>
struct accepts_one
    : std::conjunction<invocation::can_invoke<typename Trait::template callable<T>,
                                       typename Trait::arguments>,
                    invocation::is_noexcept_correct<
                        Trait::is_noexcept::value,
                        typename Trait::template callable<T>,
                        typename Trait::arguments>> {};
Naios commented 2 years ago

Could you PR your first fix that uses is_noexcept_correct_lazy? std::conjunction is a C++17 feature and cannot be used by the library (although a polyfill could be used). This is probably a GCC compiler bug (Clang compiles it fine).

MBkkt commented 2 years ago

Could you PR your first fix that uses is_noexcept_correct_lazy?

Ok

This is probably also a GCC compiler bug (Clang compiles it fine).

I think it's true, it's really strange that it try to substitute to overload with unique_function first instead of move ctor

Naios commented 2 years ago

Solved in https://github.com/Naios/function2/pull/51