diff --git a/Makefile b/Makefile index 3665251..b101ce5 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ COMMON_FLAGS = -Wall -pedantic -Wextra -Wsign-compare -Wsign-conversion -Wshadow CXXFLAGS := $(CXXFLAGS) LDFLAGS := $(LDFLAGS) +UNIT_TEST_SOURCES := $(wildcard test/t/*.cpp) +UNIT_TEST_OBJECTS := $(patsubst test/t/%.cpp,out/%.o,$(UNIT_TEST_SOURCES)) + OS:=$(shell uname -s) ifeq ($(OS),Darwin) CXXFLAGS += -stdlib=libc++ @@ -70,7 +73,7 @@ out/%.o: test/t/%.cpp Makefile optional.hpp recursive_wrapper.hpp variant.hpp va mkdir -p ./out $(CXX) -c -o $@ $< -I. -Itest/include $(DEBUG_FLAGS) $(COMMON_FLAGS) $(CXXFLAGS) -out/unit: out/unit.o out/binary_visitor_1.o out/binary_visitor_2.o out/binary_visitor_3.o out/binary_visitor_4.o out/binary_visitor_5.o out/binary_visitor_6.o out/issue21.o out/mutating_visitor.o out/optional.o out/recursive_wrapper.o out/sizeof.o out/unary_visitor.o out/variant.o +out/unit: out/unit.o $(UNIT_TEST_OBJECTS) mkdir -p ./out $(CXX) -o $@ $^ $(LDFLAGS) diff --git a/recursive_wrapper.hpp b/recursive_wrapper.hpp index 11a0f9b..6a5edb5 100644 --- a/recursive_wrapper.hpp +++ b/recursive_wrapper.hpp @@ -85,12 +85,14 @@ class recursive_wrapper : p_(new T(std::move(operand))) {} inline recursive_wrapper & operator=(recursive_wrapper const& rhs) + noexcept(std::is_nothrow_copy_assignable::value) { assign(rhs.get()); return *this; } inline recursive_wrapper & operator=(T const& rhs) + noexcept(std::is_nothrow_copy_assignable::value) { assign(rhs); return *this; @@ -110,6 +112,7 @@ class recursive_wrapper } recursive_wrapper & operator=(T && rhs) + noexcept(std::is_nothrow_move_assignable::value) { get() = std::move(rhs); return *this; diff --git a/test/t/noexcept.cpp b/test/t/noexcept.cpp new file mode 100644 index 0000000..409b441 --- /dev/null +++ b/test/t/noexcept.cpp @@ -0,0 +1,284 @@ +#include "catch.hpp" +#include "variant.hpp" + +#include + +// helper macros for concise checks, will be expanded in failure output +#define nothrow_def_c(type, ...) (std::is_nothrow_default_constructible>::value) +#define nothrow_copy_a(type, ...) (std::is_nothrow_copy_assignable>::value) +#define nothrow_copy_c(type, ...) (std::is_nothrow_copy_constructible>::value) +#define nothrow_move_a(type, ...) (std::is_nothrow_move_assignable>::value) +#define nothrow_move_c(type, ...) (std::is_nothrow_move_constructible>::value) +#define nothrow_destruct(type, ...) (detail::is_nothrow_destructible>::value) + +namespace detail { + +#if defined(__GNUC__) && (100 * __GNUC__ + __GNUC_MINOR__ < 408) + // GCC 4.7 doesn't have std::is_nothrow_destructible + + template + struct is_nothrow_destructible + { + static constexpr bool value = noexcept(std::declval().~T()); + }; + +#else + + using std::is_nothrow_destructible; + +#endif + +} // namespace detail + +namespace { // internal + +struct throwing_default_ctor +{ + throwing_default_ctor() noexcept(false) + { + throw std::runtime_error("throwing_default_ctor"); + } +}; + +struct throwing_copy +{ + throwing_copy() = default; + throwing_copy(throwing_copy && ) = default; + throwing_copy(throwing_copy const& ) noexcept(false) + { + throw std::runtime_error("throwing_copy:constructor"); + } + throwing_copy & operator=(throwing_copy && ) = default; + throwing_copy & operator=(throwing_copy const& ) noexcept(false) + { + throw std::runtime_error("throwing_copy:assignment"); + return *this; + } +}; + +struct throwing_move +{ + throwing_move() = default; + throwing_move(throwing_move const& ) = default; + throwing_move(throwing_move && ) noexcept(false) + { + throw std::runtime_error("throwing_move:constructor"); + } + throwing_move & operator=(throwing_move const& ) = default; + throwing_move & operator=(throwing_move && ) noexcept(false) + { + throw std::runtime_error("throwing_move:assignment"); + return *this; + } +}; + +struct throwing_dtor +{ + throwing_dtor() = default; + throwing_dtor(throwing_dtor const& ) = default; + throwing_dtor(throwing_dtor && ) = default; + throwing_dtor & operator=(throwing_dtor const& ) = default; + throwing_dtor & operator=(throwing_dtor && ) = default; + ~throwing_dtor() noexcept(false) + { + throw std::runtime_error("throwing_dtor"); + } +}; + +} // namespace + +TEST_CASE("exception-specification : simple variant") +{ + // variant delegates noexcept to tuple in some cases; that's why we check + // tuple as well as variant here -- when a check on variant fails, seeing + // what the tuple's noexcept is can help us locate the culprit + using std::tuple; + using mapbox::util::variant; + + SECTION("default constructor") + { + CHECK_NOFAIL(nothrow_def_c(tuple, int, float) == true); + CHECK_NOFAIL(nothrow_def_c(tuple, int, throwing_default_ctor) == false); + CHECK_NOFAIL(nothrow_def_c(tuple, throwing_default_ctor, int) == false); + CHECK_NOFAIL(nothrow_def_c(tuple, throwing_default_ctor) == false); + CHECK_NOFAIL(nothrow_def_c(tuple, throwing_copy, throwing_move) == true); + CHECK_NOFAIL(nothrow_def_c(tuple, throwing_default_ctor, throwing_copy, throwing_move) == false); + + CHECK(nothrow_def_c(variant, int, float) == true); + CHECK(nothrow_def_c(variant, int, throwing_default_ctor) == true); + CHECK(nothrow_def_c(variant, throwing_default_ctor, int) == false); + CHECK(nothrow_def_c(variant, throwing_default_ctor) == false); + CHECK(nothrow_def_c(variant, throwing_copy, throwing_move) == true); + CHECK(nothrow_def_c(variant, throwing_default_ctor, throwing_copy, throwing_move) == false); + } + + SECTION("copy constructor") + { + CHECK_NOFAIL(nothrow_copy_c(tuple, int, float) == true); + CHECK_NOFAIL(nothrow_copy_c(tuple, int, throwing_copy) == false); + CHECK_NOFAIL(nothrow_copy_c(tuple, throwing_copy) == false); + CHECK_NOFAIL(nothrow_copy_c(tuple, throwing_default_ctor, throwing_move) == true); + CHECK_NOFAIL(nothrow_copy_c(tuple, throwing_default_ctor, throwing_copy, throwing_move) == false); + + CHECK(nothrow_copy_c(variant, int, float) == true); + CHECK(nothrow_copy_c(variant, int, throwing_copy) == false); + CHECK(nothrow_copy_c(variant, throwing_copy) == false); + CHECK(nothrow_copy_c(variant, throwing_default_ctor, throwing_move) == true); + CHECK(nothrow_copy_c(variant, throwing_default_ctor, throwing_copy, throwing_move) == false); + } + + SECTION("move constructor") + { + CHECK_NOFAIL(nothrow_move_c(tuple, int, float) == true); + CHECK_NOFAIL(nothrow_move_c(tuple, int, throwing_move) == false); + CHECK_NOFAIL(nothrow_move_c(tuple, throwing_move) == false); + CHECK_NOFAIL(nothrow_move_c(tuple, throwing_default_ctor, throwing_copy) == true); + CHECK_NOFAIL(nothrow_move_c(tuple, throwing_default_ctor, throwing_copy, throwing_move) == false); + + CHECK(nothrow_move_c(variant, int, float) == true); + CHECK(nothrow_move_c(variant, int, throwing_move) == false); + CHECK(nothrow_move_c(variant, throwing_move) == false); + CHECK(nothrow_move_c(variant, throwing_default_ctor, throwing_copy) == true); + CHECK(nothrow_move_c(variant, throwing_default_ctor, throwing_copy, throwing_move) == false); + } + + SECTION("copy assignment") + { + CHECK_NOFAIL(nothrow_copy_a(tuple, int, float) == true); + CHECK_NOFAIL(nothrow_copy_a(tuple, int, throwing_copy) == false); + CHECK_NOFAIL(nothrow_copy_a(tuple, throwing_copy) == false); + CHECK_NOFAIL(nothrow_copy_a(tuple, throwing_default_ctor, throwing_move) == true); + CHECK_NOFAIL(nothrow_copy_a(tuple, throwing_default_ctor, throwing_copy, throwing_move) == false); + + CHECK(nothrow_copy_a(variant, int, float) == true); + CHECK(nothrow_copy_a(variant, int, throwing_copy) == false); + CHECK(nothrow_copy_a(variant, throwing_copy) == false); + CHECK(nothrow_copy_a(variant, throwing_default_ctor, throwing_move) == true); + CHECK(nothrow_copy_a(variant, throwing_default_ctor, throwing_copy, throwing_move) == false); + } + + SECTION("move assignment") + { + CHECK_NOFAIL(nothrow_move_a(tuple, int, float) == true); + CHECK_NOFAIL(nothrow_move_a(tuple, int, throwing_move) == false); + CHECK_NOFAIL(nothrow_move_a(tuple, throwing_move) == false); + CHECK_NOFAIL(nothrow_move_a(tuple, throwing_default_ctor, throwing_copy) == true); + CHECK_NOFAIL(nothrow_move_a(tuple, throwing_default_ctor, throwing_copy, throwing_move) == false); + + CHECK(nothrow_move_a(variant, int, float) == true); + CHECK(nothrow_move_a(variant, int, throwing_move) == false); + CHECK(nothrow_move_a(variant, throwing_move) == false); + CHECK(nothrow_move_a(variant, throwing_default_ctor, throwing_copy) == true); + CHECK(nothrow_move_a(variant, throwing_default_ctor, throwing_copy, throwing_move) == false); + } + + SECTION("destructor") + { + CHECK_NOFAIL(nothrow_destruct(tuple, int, float) == true); + CHECK_NOFAIL(nothrow_destruct(tuple, int, throwing_dtor) == false); + CHECK_NOFAIL(nothrow_destruct(tuple, throwing_dtor) == false); + + // ~variant() is noexcept(true), regardless of exception-specification + // on stored alternatives' destructors + CHECK(nothrow_destruct(variant, int, float) == true); + CHECK(nothrow_destruct(variant, int, throwing_dtor) == true); + CHECK(nothrow_destruct(variant, throwing_dtor) == true); + } +} + +namespace { // internal + +template struct wrapped_alternative; +template using wrap_alternative = mapbox::util::recursive_wrapper>; +template using recursive_variant = mapbox::util::variant>; + +template +struct wrapped_alternative : T +{ + recursive_variant var; +}; + +} // namespace + +TEST_CASE("exception-specification : recursive variant") +{ + using mapbox::util::recursive_wrapper; + + SECTION("default constructor") + { + // default-constructed wrapper allocates new T (default-constructed) + CHECK(nothrow_def_c(recursive_variant, throwing_default_ctor) == false); + CHECK(nothrow_def_c(recursive_variant, throwing_copy) == false); + CHECK(nothrow_def_c(recursive_variant, throwing_move) == false); + CHECK(nothrow_def_c(recursive_variant, throwing_dtor) == false); + } + + SECTION("copy constructor") + { + // wrapper copy-constructor allocates new T (copy-constructed) + CHECK(nothrow_copy_c(recursive_variant, throwing_default_ctor) == false); + CHECK(nothrow_copy_c(recursive_variant, throwing_copy) == false); + CHECK(nothrow_copy_c(recursive_variant, throwing_move) == false); + CHECK(nothrow_copy_c(recursive_variant, throwing_dtor) == false); + } + + SECTION("move constructor") + { + // wrapper move-constructor allocates new T (move-constructed) + CHECK(nothrow_move_c(recursive_variant, throwing_default_ctor) == false); + CHECK(nothrow_move_c(recursive_variant, throwing_copy) == false); + CHECK(nothrow_move_c(recursive_variant, throwing_move) == false); + CHECK(nothrow_move_c(recursive_variant, throwing_dtor) == false); + } + + SECTION("copy assignment") + { + // wrapper copy-assignment copies wrapped value + CHECK(nothrow_copy_a(recursive_wrapper, throwing_default_ctor) == true); + CHECK(nothrow_copy_a(recursive_wrapper, throwing_copy) == false); + CHECK(nothrow_copy_a(recursive_wrapper, throwing_move) == true); + CHECK(nothrow_copy_a(recursive_wrapper, throwing_dtor) == true); + + // variant copy-assignment destroys the original wrapper and then + // copy-constructs a new wrapper in its place, so we're actually + // testing recursive_wrapper's copy-constructor here + CHECK(nothrow_copy_a(recursive_variant, throwing_default_ctor) == false); + CHECK(nothrow_copy_a(recursive_variant, throwing_copy) == false); + CHECK(nothrow_copy_a(recursive_variant, throwing_move) == false); + CHECK(nothrow_copy_a(recursive_variant, throwing_dtor) == false); + } + + SECTION("move assignment") + { + // wrapper move-assignment swaps internal pointers + CHECK(nothrow_move_a(recursive_wrapper, throwing_default_ctor) == true); + CHECK(nothrow_move_a(recursive_wrapper, throwing_copy) == true); + CHECK(nothrow_move_a(recursive_wrapper, throwing_move) == true); + CHECK(nothrow_move_a(recursive_wrapper, throwing_dtor) == true); + + // variant move-assignment destroys the original wrapper and then + // move-constructs a new wrapper in its place, so we're actually + // testing recursive_wrapper's move-constructor here + CHECK(nothrow_move_a(recursive_variant, throwing_default_ctor) == false); + CHECK(nothrow_move_a(recursive_variant, throwing_copy) == false); + CHECK(nothrow_move_a(recursive_variant, throwing_move) == false); + CHECK(nothrow_move_a(recursive_variant, throwing_dtor) == false); + } + + SECTION("conversion assignment") + { + CHECK((std::is_nothrow_assignable & , throwing_copy const& >::value) == false); + CHECK((std::is_nothrow_assignable & , throwing_copy && >::value) == true); + CHECK((std::is_nothrow_assignable & , throwing_move const& >::value) == true); + CHECK((std::is_nothrow_assignable & , throwing_move && >::value) == false); + } + + SECTION("destructor") + { + // ~variant() is always noexcept(true) + CHECK(nothrow_destruct(recursive_variant, throwing_default_ctor) == true); + CHECK(nothrow_destruct(recursive_variant, throwing_copy) == true); + CHECK(nothrow_destruct(recursive_variant, throwing_move) == true); + CHECK(nothrow_destruct(recursive_variant, throwing_dtor) == true); + } +} diff --git a/variant.gyp b/variant.gyp index b1f3801..3e0977c 100644 --- a/variant.gyp +++ b/variant.gyp @@ -16,6 +16,7 @@ "test/t/binary_visitor_6.cpp", "test/t/issue21.cpp", "test/t/mutating_visitor.cpp", + "test/t/noexcept.cpp", "test/t/optional.cpp", "test/t/recursive_wrapper.cpp", "test/t/sizeof.cpp", diff --git a/variant.hpp b/variant.hpp index d2c91cd..8e0b32b 100644 --- a/variant.hpp +++ b/variant.hpp @@ -617,6 +617,7 @@ class variant } VARIANT_INLINE variant(variant const& old) + noexcept(std::is_nothrow_copy_constructible>::value) : type_index(old.type_index) { helper_type::copy(old.type_index, &old.data, &data); @@ -629,52 +630,24 @@ class variant helper_type::move(old.type_index, &old.data, &data); } -private: - VARIANT_INLINE void copy_assign(variant const& rhs) - { - helper_type::destroy(type_index, &data); - type_index = detail::invalid_value; - helper_type::copy(rhs.type_index, &rhs.data, &data); - type_index = rhs.type_index; - } - - VARIANT_INLINE void move_assign(variant && rhs) +public: + VARIANT_INLINE variant& operator=(variant && other) + noexcept(std::is_nothrow_move_constructible>::value) { helper_type::destroy(type_index, &data); type_index = detail::invalid_value; - helper_type::move(rhs.type_index, &rhs.data, &data); - type_index = rhs.type_index; - } - -public: - VARIANT_INLINE variant& operator=(variant && other) - { - move_assign(std::move(other)); + helper_type::move(other.type_index, &other.data, &data); // move-construction + type_index = other.type_index; return *this; } VARIANT_INLINE variant& operator=(variant const& other) + noexcept(std::is_nothrow_copy_constructible>::value) { - copy_assign(other); - return *this; - } - - // conversions - // move-assign - template - VARIANT_INLINE variant& operator=(T && rhs) noexcept - { - variant temp(std::forward(rhs)); - move_assign(std::move(temp)); - return *this; - } - - // copy-assign - template - VARIANT_INLINE variant& operator=(T const& rhs) - { - variant temp(rhs); - copy_assign(temp); + helper_type::destroy(type_index, &data); + type_index = detail::invalid_value; + helper_type::copy(other.type_index, &other.data, &data); // copy-construction + type_index = other.type_index; return *this; }