diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 8ba9871..6c422a2 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -4,9 +4,9 @@ on: push: branches: [master] paths: + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' @@ -14,10 +14,10 @@ on: pull_request: branches: [master] paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/analysis.yml' diff --git a/.github/workflows/build-macos.yaml b/.github/workflows/build-macos.yaml index bbcaafc..490ad36 100644 --- a/.github/workflows/build-macos.yaml +++ b/.github/workflows/build-macos.yaml @@ -3,22 +3,23 @@ name: macOS on: push: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/build-macos.yml' pull_request: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/build-macos.yml' + jobs: test: name: macoS Xcode ${{matrix.compiler.version}} ${{matrix.arch}} ${{matrix.build_type}} diff --git a/.github/workflows/build-ubuntu.yaml b/.github/workflows/build-ubuntu.yaml index 5364383..27fa8fe 100644 --- a/.github/workflows/build-ubuntu.yaml +++ b/.github/workflows/build-ubuntu.yaml @@ -3,22 +3,23 @@ name: Ubuntu on: push: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/build-ubuntu.yml' pull_request: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/build-ubuntu.yml' + jobs: test: name: Ubuntu ${{matrix.compiler.cc}} ${{matrix.arch}} ${{matrix.build_type}} @@ -100,6 +101,8 @@ jobs: steps: - name: Clone uses: actions/checkout@v2 + with: + submodules: true - name: Prepare Environment run: | diff --git a/.github/workflows/build-windows.yaml b/.github/workflows/build-windows.yaml index 142fbb6..30b1fec 100644 --- a/.github/workflows/build-windows.yaml +++ b/.github/workflows/build-windows.yaml @@ -3,19 +3,19 @@ name: Windows on: push: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/build-windows.yml' pull_request: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/build-windows.yml' @@ -105,7 +105,7 @@ jobs: - name: Build working-directory: ${{env.build-directory}} - run: cmake --build . --config ${{matrix.build_type}} + run: cmake --build . --target Delegate.test --config ${{matrix.build_type}} - name: Test working-directory: ${{env.build-directory}} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 542998a..85eb1c9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -3,19 +3,19 @@ name: "Code Coverage" on: push: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/coverage.yml' pull_request: paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/coverage.yml' diff --git a/.github/workflows/scan-build.yml b/.github/workflows/scan-build.yml index bb41498..8639126 100644 --- a/.github/workflows/scan-build.yml +++ b/.github/workflows/scan-build.yml @@ -4,20 +4,20 @@ on: push: branches: [master] paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/scan-build.yml' pull_request: branches: [master] paths: - - '.gitmodules' + - 'include/**.hpp' + - 'test/**.cpp' - '**.cmake' - - '**.cpp' - - '**.hpp' + - '.gitmodules' - 'CMakeLists.txt' - 'test/CMakeLists.txt' - '.github/workflows/scan-build.yml' diff --git a/CMakeLists.txt b/CMakeLists.txt index 52df5ef..4f75a89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,39 +57,11 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND # Disable unnecessary compatibility and 'newline-eof'. This is a modern C++ # library -- so these serve no purpose - add_compile_options(-Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-newline-eof) - - # Disable warnings on padding. This is a great warning, but not good when - # coupled with -Werror - add_compile_options(-Wno-padded) - - # Several utilities require static objects that have exit-time destructors, - # including 'error_category' types from the standard. - add_compile_options(-Wno-exit-time-destructors) - - # This is erroring on valid uses of the standard library, like std::lock_guard. - add_compile_options(-Wno-ctad-maybe-unsupported) - - # This is a good warning *in general*, but not when using MSVC's standard - # library, which requires defining "_" prefixed macros just to have it operate - # like the C++ standard dictates. - add_compile_options(-Wno-reserved-id-macro) - - # Don't warn on ignored attributes; this breaks builds using dllexport/dllimport on - # classes that may also define inline functions - add_compile_options(-Wno-ignored-attributes) + add_compile_options(-Wno-c++98-compat -Wno-c++98-compat-pedantic) elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") add_compile_options(-Wall -Werror -Wextra -pedantic -pedantic-errors) - - # Xcode 11.2 seems to enable -Wnewline-eof with '-Wextra'. Disable this, - # since it's a legacy requirement not needed in C++11 onward. - add_compile_options(-Wno-newline-eof) - - # Don't warn on ignored attributes; this breaks builds using dllexport/dllimport on - # classes that may also define inline functions - add_compile_options(-Wno-ignored-attributes) elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") add_compile_options(/W4 /WX) endif () diff --git a/include/delegate.hpp b/include/delegate.hpp index fa56748..9807b08 100644 --- a/include/delegate.hpp +++ b/include/delegate.hpp @@ -14,16 +14,15 @@ #ifndef DELEGATE_DELEGATE_HPP #define DELEGATE_DELEGATE_HPP -#if defined(_MSC_VER) && (_MSC_VER >= 1200) +#if defined(_MSC_VER) # pragma once -#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) +#endif // defined(_MSC_VER) #include // std::enable_if, std::is_constructible, etc #include // std::invoke #include // placement new, std::launder #include // assert #include // std::forward -#include // std::max_align_t #include // std::memcmp #include // std::runtime_error @@ -48,6 +47,15 @@ # define DELEGATE_ASSERT(x) (x ? ((void)0) : [&]{ assert(x); }()) #endif +#if defined(_MSC_VER) +# pragma warning(push) +// MSVC complains about a [[noreturn]] function returning non-void, but this +// is needed to satisfy the stub interface +# pragma warning(disable:4646) +// MSVC warns that `alignas` will cause padding. Like alignment does. +# pragma warning(disable:4324) +#endif + namespace DELEGATE_NAMESPACE_INTERNAL { inline namespace bitwizeshift { namespace detail { @@ -146,7 +154,7 @@ constexpr auto bind(Callable* fn) noexcept -> callable_ref_bind_target /// \brief Binds an opaque function pointer to create a function bind target /// -/// \param the opaque function to pointer to bind +/// \param fn the opaque function to pointer to bind /// \return the created target template constexpr auto bind(R(*fn)(Args...)) noexcept -> opaque_function_bind_target; @@ -169,7 +177,7 @@ template && std::is_default_constructible_v )>> -constexpr auto bind(Callable) noexcept -> empty_callable_bind_target; +constexpr auto bind(Callable callable) noexcept -> empty_callable_bind_target; /// \brief Binds a trivially-copyable callable object as a bind target /// @@ -235,10 +243,14 @@ class delegate : sizeof(void*) ); + static constexpr auto storage_align = ( + alignof(void*) + ); + template using fits_storage = std::bool_constant<( (sizeof(U) <= storage_size) && - (alignof(U) <= alignof(std::max_align_t)) + (alignof(U) <= storage_align) )>; //---------------------------------------------------------------------------- @@ -300,7 +312,6 @@ class delegate /// ``` /// /// \param target the target to bind - /// \return the constructed delegate template @@ -611,6 +622,13 @@ class delegate /// This is equivalent to call `has_target()` constexpr explicit operator bool() const noexcept; +#if defined(__clang__) +# pragma clang diagnostic push +// clang incorrectly flags '\return' when 'R = void' as being an error, but +// there is no way to conditionally document a return type +# pragma clang diagnostic ignored "-Wdocumentation" +#endif + /// \brief Invokes the underlying bound function /// /// \param args the arguments to forward to the function @@ -619,6 +637,10 @@ class delegate typename = std::enable_if_t>> constexpr auto operator()(UArgs&&...args) const -> R; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + //---------------------------------------------------------------------------- /// \brief Queries whether this `delegate` has any function bound to it @@ -757,16 +779,23 @@ class delegate // pointers using any_function = void(*)(); - using stub_function = R(*)(const void*, Args...); + using stub_function = R(*)(const delegate*, Args...); //---------------------------------------------------------------------------- // Stub Functions //---------------------------------------------------------------------------- private: +#if defined(__clang__) +# pragma clang diagnostic push +// clang incorrectly flags '\return' when 'R = void' as being an error, but +// there is no way to conditionally document a return type +# pragma clang diagnostic ignored "-Wdocumentation" +#endif + /// \brief Throws an exception by default [[noreturn]] - static auto null_stub(const void*, Args...) -> R; + static auto null_stub(const delegate*, Args...) -> R; /// \brief A stub function for statically-specific functions (or other /// callables) @@ -775,27 +804,27 @@ class delegate /// \param args the arguments to forward to the function /// \return the result of the Function call template - static auto function_stub(const void*, Args...args) -> R; + static auto function_stub(const delegate*, Args...args) -> R; /// \brief A stub function for statically-specific member functions (or other /// callables) /// /// \tparam MemberFunction the statically-specific member function /// \tparam T the type of the first pointer - /// \param storage the base storage that contains the instance pointer + /// \param self an instance to the delegate that contains the instance pointer /// \param args the arguments to forward to the function /// \return the result of the MemberFunction call template - static auto member_function_stub(const void* storage, Args...args) -> R; + static auto member_function_stub(const delegate* self, Args...args) -> R; /// \brief A stub function for non-owning view of callable objects /// /// \tparam Fn the function to reference - /// \param storage the base storage that contains \p fn + /// \param self an instance to the delegate that contains \p fn /// \param args the arguments to forward to the function /// \return the result of invoking \p fn template - static auto callable_view_stub(const void* storage, Args...args) -> R; + static auto callable_view_stub(const delegate* self, Args...args) -> R; /// \brief A stub function empty callable objects /// @@ -803,26 +832,30 @@ class delegate /// \param args the arguments to forward to the function /// \return the result of invoking \p fn template - static auto empty_callable_stub(const void*, Args...args) -> R; + static auto empty_callable_stub(const delegate*, Args...args) -> R; /// \brief A stub function for small callable objects /// /// \tparam Fn the small-storage function to invoke - /// \param storage the base storage that contains the function + /// \param self an instance to the delegate that contains the function /// \param args the arguments to forward to the function /// \return the result of invoking \p fn template - static auto small_callable_stub(const void* storage, Args...args) -> R; + static auto small_callable_stub(const delegate* self, Args...args) -> R; /// \brief A stub function for function pointers /// /// \tparam R2 the return type /// \tparam Args2 the arguments - /// \param storage the storage object that contains the function pointers + /// \param self an instance to the delegate that contains the function pointers /// \param args the arguments to forward to the function /// \return the result of invoking the function template - static auto function_ptr_stub(const void* storage, Args...args) -> R; + static auto function_ptr_stub(const delegate* self, Args...args) -> R; + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif //---------------------------------------------------------------------------- // Private Members @@ -831,16 +864,16 @@ class delegate struct empty_type{}; - /// \internal + ////////////////////////////////////////////////////////////////////////////// /// The underlying storage, which may be either a (possibly const) pointer, /// or a char buffer of storage. + ////////////////////////////////////////////////////////////////////////////// union { - empty_type m_empty; // Default type does nothing void* m_instance{}; const void* m_const_instance; any_function m_function; - alignas(std::max_align_t) unsigned char m_storage[storage_size]; + alignas(storage_align) unsigned char m_storage[storage_size]; }; stub_function m_stub; }; @@ -1174,7 +1207,7 @@ inline constexpr DELEGATE_INLINE_VISIBILITY auto delegate::operator()(UArgs&&...args) const -> R { - return std::invoke(m_stub, m_storage, std::forward(args)...); + return std::invoke(m_stub, this, std::forward(args)...); } //------------------------------------------------------------------------------ @@ -1313,7 +1346,7 @@ auto delegate::has_target(R2(*fn)(Args2...)) template inline -auto delegate::null_stub(const void*, Args...) +auto delegate::null_stub(const delegate*, Args...) -> R { // Default stub throws unconditionally @@ -1323,7 +1356,7 @@ auto delegate::null_stub(const void*, Args...) template template inline -auto delegate::function_stub(const void*, Args...args) +auto delegate::function_stub(const delegate*, Args...args) -> R { if constexpr (std::is_void_v) { @@ -1336,14 +1369,13 @@ auto delegate::function_stub(const void*, Args...args) template template inline -auto delegate::member_function_stub(const void* storage, +auto delegate::member_function_stub(const delegate* self, Args...args) -> R { // This stub is used for both `const` and non-`const` `T` types. To extract // the pointer from the correct data member of the union, this uses an // immediately-invoking lambda (IIL) with `if constexpr` - const auto* const self = static_cast(storage); auto* const c = [&self]{ if constexpr (std::is_const_v) { return static_cast(self->m_const_instance); @@ -1362,14 +1394,13 @@ auto delegate::member_function_stub(const void* storage, template template inline -auto delegate::callable_view_stub(const void* storage, +auto delegate::callable_view_stub(const delegate* self, Args...args) -> R { // This stub is used for both `const` and non-`const` `Fn` types. To extract // the pointer from the correct data member of the union, this uses an // immediately-invoking lambda (IIL) with `if constexpr` - const auto* const self = static_cast(storage); auto* const f = [&self]{ if constexpr (std::is_const_v) { return static_cast(self->m_const_instance); @@ -1388,7 +1419,7 @@ auto delegate::callable_view_stub(const void* storage, template template inline -auto delegate::empty_callable_stub(const void*, +auto delegate::empty_callable_stub(const delegate*, Args...args) -> R { @@ -1402,11 +1433,10 @@ auto delegate::empty_callable_stub(const void*, template template inline -auto delegate::small_callable_stub(const void* storage, +auto delegate::small_callable_stub(const delegate* self, Args...args) -> R { - const auto* const self = static_cast(storage); const auto& f = *std::launder(reinterpret_cast(self->m_storage)); if constexpr (std::is_void_v) { @@ -1419,11 +1449,10 @@ auto delegate::small_callable_stub(const void* storage, template template inline -auto delegate::function_ptr_stub(const void* storage, +auto delegate::function_ptr_stub(const delegate* self, Args...args) -> R { - const auto* const self = static_cast(storage); const auto f = reinterpret_cast(self->m_function); if constexpr (std::is_void_v) { @@ -1436,4 +1465,8 @@ auto delegate::function_ptr_stub(const void* storage, } // inline namespace bitwizeshift } // namespace DELEGATE_NAMESPACE_INTERNAL +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + #endif /* DELEGATE_DELEGATE_HPP */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4f5542d..2ddc81a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,17 @@ add_executable(${PROJECT_NAME}.test ) add_executable(${PROJECT_NAME}::test ALIAS ${PROJECT_NAME}.test) +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(${PROJECT_NAME}.test PRIVATE + "-Wno-shorten-64-to-32" + "-Wno-sign-conversion" + ) +endif () + +if (MINGW AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${PROJECT_NAME}.test PRIVATE "-Wa,-mbig-obj") +endif () + target_link_libraries(${PROJECT_NAME}.test PRIVATE ${PROJECT_NAME}::${PROJECT_NAME} PRIVATE Catch2::Catch2 diff --git a/test/src/delegate.test.cpp b/test/src/delegate.test.cpp index c1f9f75..21c5ae6 100644 --- a/test/src/delegate.test.cpp +++ b/test/src/delegate.test.cpp @@ -3,6 +3,13 @@ #include #include +#if defined(_MSC_VER) +# pragma warning(push) +// MSVC warns on implicit casts for the covariant functions. Since this is a +// test, this diagnostic is useless +# pragma warning(disable:4267) +#endif + namespace DELEGATE_NAMESPACE_INTERNAL { inline namespace bitwizeshift { namespace test { @@ -611,3 +618,7 @@ TEST_CASE("delegate::has_target()") { } // namespace test } // inline namespace bitwizeshift } // namespace DELEGATE_NAMESPACE_INTERNAL + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif