From c04abe510efbe591af3dd31a4c2d1d6028b0e9aa Mon Sep 17 00:00:00 2001 From: Yaraslau Tamashevich Date: Sun, 11 Jan 2026 19:00:01 +0100 Subject: [PATCH] Add missing implementation for converting constructor and tests --- include/rsl/tuple | 99 +++-- test/common/types.h | 289 ++++++++++++++ test/tuple/CMakeLists.txt | 7 +- test/tuple/constructor.cpp | 606 ++++++++++++++++++++++++++++++ test/tuple/tuple.get/category.cpp | 20 +- 5 files changed, 982 insertions(+), 39 deletions(-) create mode 100644 test/tuple/constructor.cpp diff --git a/include/rsl/tuple b/include/rsl/tuple index 2230085..9bd7051 100644 --- a/include/rsl/tuple +++ b/include/rsl/tuple @@ -172,6 +172,16 @@ private: } } + template + constexpr storage_type _impl_converting_constructor(T&& o, std::index_sequence) { + return {([&]()->Types... [Is] { + if constexpr (std::is_reference_v) + return o.template get(); + else + return Types... [Is] { std::forward_like(o.template get()) }; + }())...}; + } + public: storage_type _impl_storage; static constexpr auto _impl_accessor = [:_impl::cache_members(nonstatic_data_members_of( @@ -190,64 +200,95 @@ public: // sizeof...(Types) >= 1; // [tuple.cnstr], tuple construction - constexpr explicit((!_tuple_impl::is_implicitly_default_constructible || ...)) + constexpr explicit((!_tuple_impl::is_implicitly_default_constructible || + ...)) // [tuple.cnstr]/8 tuple() noexcept(std::is_nothrow_default_constructible_v) - requires(std::is_default_constructible_v) + requires(sizeof...(Types) >= 1 && + std::is_default_constructible_v) // [tuple.cnstr]/6 = default; - constexpr explicit(!(std::is_convertible_v && ...)) + constexpr tuple() noexcept + requires(sizeof...(Types) == 0) + = default; // [tuple.cnstr]/5 + + constexpr explicit(!(std::is_convertible_v && ...)) // [tuple.cnstr]/11 tuple(Types const&... values) noexcept((std::is_nothrow_copy_constructible_v && ...)) - requires(sizeof...(Types) >= 1 && (std::is_copy_constructible_v && ...)) + requires(sizeof...(Types) >= 1 && + (std::is_copy_constructible_v && ...)) // [tuple.cnstr]/9 : _impl_storage{values...} {} template - requires(sizeof...(UTypes) == sizeof...(Types) && - !(std::reference_constructs_from_temporary_v || - ...) && // TODO required? + requires(sizeof...(UTypes) == sizeof...(Types) && // [tuple.cnstr]/13.1 + sizeof...(UTypes) >= 1 && // [tuple.cnstr]/13.2 enable_utypes_ctor && - (std::is_constructible_v && ...)) - constexpr explicit(!(std::is_convertible_v && ...)) tuple(UTypes&&... values) + (std::is_constructible_v && ...)) // [tuple.cnstr]/13.3 + constexpr explicit(!(std::is_convertible_v && ...)) // [tuple.cnstr]/15 + tuple(UTypes&&... values) : _impl_storage(std::forward(values)...) {} template requires(sizeof...(UTypes) == sizeof...(Types) && - (std::reference_constructs_from_temporary_v || ...)) + (std::reference_constructs_from_temporary_v || // [tuple.cnstr]/15 + ...)) constexpr tuple(UTypes&&... values) = delete; - tuple(tuple const&) noexcept(std::is_nothrow_copy_constructible_v) - requires(std::is_copy_constructible_v) + constexpr tuple(tuple const&) noexcept(std::is_nothrow_copy_constructible_v) + requires(std::is_copy_constructible_v) // [tuple.cnstr]/16 = default; - tuple(tuple&&) noexcept(std::is_nothrow_move_constructible_v) - requires(std::is_move_constructible_v) + constexpr tuple(tuple&&) noexcept(std::is_nothrow_move_constructible_v) + requires(std::is_move_constructible_v) // [tuple.cnstr]/18 = default; template - constexpr explicit(!(std::is_convertible_v && ...)) + requires(sizeof...(UTypes) == sizeof...(Types)) && // [tuple.cnstr]/21.1 + (std::is_constructible_v && ...) && // [tuple.cnstr]/21.2 + (sizeof...(UTypes) != 1 || // [tuple.cnstr]/21.3 + (!std::is_convertible_v, Types...[0]> && // [tuple.cnstr]/21.3 + !std::is_constructible_v> && // [tuple.cnstr]/21.3 + !std::same_as)) // [tuple.cnstr]/21.3 + constexpr explicit(!(std::is_convertible_v && ...)) // [tuple.cnstr]/23 tuple(tuple& other) noexcept((std::is_nothrow_constructible_v && ...)) - requires(false) // TODO - : _impl_storage(other) {} + : _impl_storage(_impl_converting_constructor(other, std::index_sequence_for{})) {} template - constexpr explicit(!(std::is_convertible_v && ...)) + requires(sizeof...(UTypes) == sizeof...(Types)) && // [tuple.cnstr]/21.1 + (std::is_constructible_v && ...) && // [tuple.cnstr]/21.2 + (sizeof...(UTypes) != 1 || // [tuple.cnstr]/21.3 + (!std::is_convertible_v, Types...[0]> && // [tuple.cnstr]/21.3 + !std::is_constructible_v> && // [tuple.cnstr]/21.3 + !std::same_as)) // [tuple.cnstr]/21.3 + constexpr explicit(!(std::is_convertible_v && ...)) // [tuple.cnstr]/23 tuple(tuple const& other) noexcept( - (std::is_nothrow_constructible_v && ...)) - requires(false) // TODO - : _impl_storage(other) {} + (std::is_nothrow_constructible_v && ...)) + : _impl_storage(_impl_converting_constructor(other, std::index_sequence_for{})) {} template - constexpr explicit(!(std::is_convertible_v && ...)) - tuple(tuple&& other) noexcept((std::is_nothrow_constructible_v && + requires(sizeof...(UTypes) == sizeof...(Types)) && // [tuple.cnstr]/21.1 + (std::is_constructible_v && ...) && // [tuple.cnstr]/21.2 + (sizeof...(UTypes) != 1 || // [tuple.cnstr]/21.3 + (!std::is_convertible_v, Types...[0]> && // [tuple.cnstr]/21.3 + !std::is_constructible_v> && // [tuple.cnstr]/21.3 + !std::same_as)) // [tuple.cnstr]/21.3 + constexpr explicit(!(std::is_convertible_v && ...)) // [tuple.cnstr]/23 + tuple(tuple&& other) noexcept((std::is_nothrow_constructible_v && ...)) - requires(false) // TODO - : _impl_storage(std::move(other)) {} + : _impl_storage( + _impl_converting_constructor(std::move(other), std::index_sequence_for{})) {} template - constexpr explicit(!(std::is_convertible_v && ...)) - tuple(tuple const&& other) - requires(false) // TODO - : _impl_storage(std::move(other)) {} + requires(sizeof...(UTypes) == sizeof...(Types)) && // [tuple.cnstr]/21.1 + (std::is_constructible_v && ...) && // [tuple.cnstr]/21.2 + (sizeof...(UTypes) != 1 || // [tuple.cnstr]/21.3 + (!std::is_convertible_v, Types...[0]> && // [tuple.cnstr]/21.3 + !std::is_constructible_v> && // [tuple.cnstr]/21.3 + !std::same_as)) // [tuple.cnstr]/21.3 + constexpr explicit(!(std::is_convertible_v && ...)) // [tuple.cnstr]/23 + tuple(tuple const&& other) noexcept( + (std::is_nothrow_constructible_v && ...)) + : _impl_storage( + _impl_converting_constructor(std::move(other), std::index_sequence_for{})) {} // template // constexpr explicit(true /* TODO*/) tuple(std::pair&); // only if diff --git a/test/common/types.h b/test/common/types.h index 8bf4e6f..ebda3a3 100644 --- a/test/common/types.h +++ b/test/common/types.h @@ -1,4 +1,24 @@ #pragma once +#include + +struct Explicit { + int value; + explicit Explicit(int x) : value(x) {} +}; + +struct Implicit { + int value; + Implicit(int x) : value(x) {} +}; + +struct ExplicitTwo { + ExplicitTwo() {} + ExplicitTwo(ExplicitTwo const&) {} + ExplicitTwo(ExplicitTwo&&) {} + + template ::value>::type> + explicit ExplicitTwo(T) {} +}; struct MoveOnly { MoveOnly() : val(0) {} @@ -112,3 +132,272 @@ struct TracedAssignment { return *this; } }; + +// move_only type which triggers the empty base optimization +struct MoveOnlyEbo { + MoveOnlyEbo() = default; + MoveOnlyEbo(MoveOnlyEbo&&) = default; +}; + +// a move_only type which does not trigger the empty base optimization +struct MoveOnlyLarge final { + MoveOnlyLarge() : value(42) {} + MoveOnlyLarge(MoveOnlyLarge&&) = default; + int value; +}; + +struct TrackDtor { + int* count_; + constexpr explicit TrackDtor(int* count) : count_(count) {} + constexpr TrackDtor(TrackDtor&& that) : count_(that.count_) { that.count_ = nullptr; } + constexpr ~TrackDtor() { + if (count_) + ++*count_; + } +}; + +struct NoDefault { + NoDefault() = delete; + explicit NoDefault(int) {} +}; + +struct NoExceptDefault { + NoExceptDefault() noexcept = default; +}; + +struct ThrowingDefault { + ThrowingDefault() {} +}; + +struct IllFormedDefault { + IllFormedDefault(int x) : value(x) {} + template + constexpr IllFormedDefault() { + static_assert(Pred, "The default constructor should not be instantiated"); + } + int value; +}; + +class DefaultOnly { + int data_; + + DefaultOnly(const DefaultOnly&); + DefaultOnly& operator=(const DefaultOnly&); + +public: + static int count; + + DefaultOnly() : data_(-1) { ++count; } + ~DefaultOnly() { + data_ = 0; + --count; + } + + friend bool operator==(const DefaultOnly& x, const DefaultOnly& y) { return x.data_ == y.data_; } + friend bool operator<(const DefaultOnly& x, const DefaultOnly& y) { return x.data_ < y.data_; } +}; + +struct NonConstCopyable { + NonConstCopyable() = default; + explicit NonConstCopyable(int v) : value(v) {} + NonConstCopyable(NonConstCopyable&) = default; + NonConstCopyable(NonConstCopyable const&) = delete; + int value; +}; + +template +struct BlowsUpOnConstCopy { + BlowsUpOnConstCopy() = default; + constexpr BlowsUpOnConstCopy(BlowsUpOnConstCopy const&) { + static_assert(!std::is_same::value, ""); + } + BlowsUpOnConstCopy(BlowsUpOnConstCopy&) = default; +}; + +struct Empty {}; + +struct MutableCopy { + int val; + bool alloc_constructed{false}; + + constexpr MutableCopy() = default; + constexpr MutableCopy(int _val) : val(_val) {} + constexpr MutableCopy(MutableCopy&) = default; + constexpr MutableCopy(const MutableCopy&) = delete; +}; + +struct ConstCopy { + int val; + bool alloc_constructed{false}; + + constexpr ConstCopy() = default; + constexpr ConstCopy(int _val) : val(_val) {} + constexpr ConstCopy(const ConstCopy&) = default; + constexpr ConstCopy(ConstCopy&) = delete; +}; + +struct MutableMove { + int val; + bool alloc_constructed{false}; + + constexpr MutableMove() = default; + constexpr MutableMove(int _val) : val(_val) {} + constexpr MutableMove(MutableMove&&) = default; + constexpr MutableMove(const MutableMove&&) = delete; +}; + +struct ConstMove { + int val; + bool alloc_constructed{false}; + + constexpr ConstMove() = default; + constexpr ConstMove(int _val) : val(_val) {} + constexpr ConstMove(const ConstMove&& o) : val(o.val) {} + constexpr ConstMove(ConstMove&&) = delete; +}; + +template +struct ConvertibleFrom { + T v; + bool alloc_constructed{false}; + + constexpr ConvertibleFrom() = default; + constexpr ConvertibleFrom(T& _v) + requires(std::is_constructible_v) + : v(_v) {} + constexpr ConvertibleFrom(const T& _v) + requires(std::is_constructible_v && !std::is_const_v) + : v(_v) {} + constexpr ConvertibleFrom(T&& _v) + requires(std::is_constructible_v) + : v(std::move(_v)) {} + constexpr ConvertibleFrom(const T&& _v) + requires(std::is_constructible_v && !std::is_const_v) + : v(std::move(_v)) {} +}; + +template +struct ExplicitConstructibleFrom { + T v; + bool alloc_constructed{false}; + + constexpr explicit ExplicitConstructibleFrom() = default; + constexpr explicit ExplicitConstructibleFrom(T& _v) + requires(std::is_constructible_v) + : v(_v) {} + constexpr explicit ExplicitConstructibleFrom(const T& _v) + requires(std::is_constructible_v && !std::is_const_v) + : v(_v) {} + constexpr explicit ExplicitConstructibleFrom(T&& _v) + requires(std::is_constructible_v) + : v(std::move(_v)) {} + constexpr explicit ExplicitConstructibleFrom(const T&& _v) + requires(std::is_constructible_v && !std::is_const_v) + : v(std::move(_v)) {} +}; + +struct TracedCopyMove { + int nonConstCopy = 0; + int constCopy = 0; + int nonConstMove = 0; + int constMove = 0; + bool alloc_constructed = false; + + constexpr TracedCopyMove() = default; + constexpr TracedCopyMove(const TracedCopyMove& other) + : nonConstCopy(other.nonConstCopy) + , constCopy(other.constCopy + 1) + , nonConstMove(other.nonConstMove) + , constMove(other.constMove) {} + constexpr TracedCopyMove(TracedCopyMove& other) + : nonConstCopy(other.nonConstCopy + 1) + , constCopy(other.constCopy) + , nonConstMove(other.nonConstMove) + , constMove(other.constMove) {} + + constexpr TracedCopyMove(TracedCopyMove&& other) + : nonConstCopy(other.nonConstCopy) + , constCopy(other.constCopy) + , nonConstMove(other.nonConstMove + 1) + , constMove(other.constMove) {} + + constexpr TracedCopyMove(const TracedCopyMove&& other) + : nonConstCopy(other.nonConstCopy) + , constCopy(other.constCopy) + , nonConstMove(other.nonConstMove) + , constMove(other.constMove + 1) {} +}; + +// If the constructor tuple(tuple&) is not available, +// the fallback call to `tuple(const tuple&) = default;` or any other +// constructor that takes const ref would increment the constCopy. +inline constexpr bool non_const_copy_constructor_called(const TracedCopyMove& obj) { + return obj.nonConstCopy == 1 && obj.constCopy == 0 && obj.constMove == 0 && obj.nonConstMove == 0; +} + +// If the constructor tuple(const tuple&&) is not available, +// the fallback call to `tuple(const tuple&) = default;` or any other +// constructor that takes const ref would increment the constCopy. +inline constexpr bool const_move_constructor_called(const TracedCopyMove& obj) { + return obj.nonConstMove == 0 && obj.constMove == 1 && obj.constCopy == 0 && obj.nonConstCopy == 0; +} + +struct NoConstructorFromInt {}; + +struct CvtFromTupleRef : TracedCopyMove { + constexpr CvtFromTupleRef() = default; + constexpr CvtFromTupleRef(std::tuple& other) + : TracedCopyMove(static_cast(std::get<0>(other))) {} +}; + +struct ExplicitCtrFromTupleRef : TracedCopyMove { + constexpr explicit ExplicitCtrFromTupleRef() = default; + constexpr explicit ExplicitCtrFromTupleRef(std::tuple& other) + : TracedCopyMove(static_cast(std::get<0>(other))) {} +}; + +struct CvtFromConstTupleRefRef : TracedCopyMove { + constexpr CvtFromConstTupleRefRef() = default; + constexpr CvtFromConstTupleRefRef(const std::tuple&& other) + : TracedCopyMove(static_cast(std::get<0>(other))) {} +}; + +struct ExplicitCtrFromConstTupleRefRef : TracedCopyMove { + constexpr explicit ExplicitCtrFromConstTupleRefRef() = default; + constexpr explicit ExplicitCtrFromConstTupleRefRef( + std::tuple&& other) + : TracedCopyMove(static_cast(std::get<0>(other))) {} +}; + +template +void conversion_test(T); + +template +concept ImplicitlyConstructible = + requires(Args&&... args) { conversion_test({std::forward(args)...}); }; + +struct CopyAssign { + int val; + + constexpr CopyAssign() = default; + constexpr CopyAssign(int v) : val(v) {} + + constexpr CopyAssign& operator=(const CopyAssign&) = default; + + constexpr const CopyAssign& operator=(const CopyAssign&) const = delete; + constexpr CopyAssign& operator=(CopyAssign&&) = delete; + constexpr const CopyAssign& operator=(CopyAssign&&) const = delete; +}; + +struct MoveAssign { + int val; + + constexpr MoveAssign() = default; + constexpr MoveAssign(int v) : val(v) {} + + constexpr MoveAssign& operator=(MoveAssign&&) = default; + + constexpr MoveAssign& operator=(const MoveAssign&) = delete; + constexpr const MoveAssign& operator=(const MoveAssign&) const = delete; + constexpr const MoveAssign& operator=(MoveAssign&&) const = delete; +}; diff --git a/test/tuple/CMakeLists.txt b/test/tuple/CMakeLists.txt index 80cd34e..fb7fdfb 100644 --- a/test/tuple/CMakeLists.txt +++ b/test/tuple/CMakeLists.txt @@ -1,7 +1,8 @@ -target_sources(rsl-util-test PRIVATE - structured_bindings.cpp - empty.cpp +target_sources(rsl-util-test PRIVATE assign.cpp + constructor.cpp + empty.cpp + structured_bindings.cpp tie.cpp make_tuple.cpp ) diff --git a/test/tuple/constructor.cpp b/test/tuple/constructor.cpp new file mode 100644 index 0000000..d677cdb --- /dev/null +++ b/test/tuple/constructor.cpp @@ -0,0 +1,606 @@ +#include +#include +#include +#include +#include +int DefaultOnly::count = 0; +TEST(Tuple, constructor_lazy_sfinae) { + { + NonConstCopyable v(42); + BlowsUpOnConstCopy b; + rsl::tuple> t(v, b); + ASSERT_EQ(rsl::get<0>(t).value, 42); + } +} + +template +constexpr bool CreateTuple(rsl::index_sequence) { + using LargeTuple = rsl::tuple...>; + using TargetTuple = rsl::tuple; + LargeTuple tuple(std::integral_constant{}...); + static_assert(get<0>(tuple).value == 0); + static_assert(get(tuple).value == sizeof...(I) - 1); + + TargetTuple t1 = tuple; // converting copy constructor from & + TargetTuple t2 = + static_cast(tuple); // converting copy constructor from const& + TargetTuple t3 = std::move(tuple); // converting rvalue constructor + TargetTuple t4 = static_cast(tuple); // converting const rvalue constructor + TargetTuple t5; // default constructor + (void)t1; + (void)t2; + (void)t3; + (void)t4; + (void)t5; + + t1 = tuple; // converting assignment from & + t1 = static_cast(tuple); // converting assignment from const& + t1 = std::move(tuple); // converting assignment from && + t1 = static_cast(tuple); // converting assignment from const&& + std::swap(t1, t2); // swap + + return true; +} + +// TEST(Tuple, constructor_recursion_depth) { +// static_assert(CreateTuple(rsl::make_index_sequence<512>{})); +// } + +template +void test_sfinae() { + using Tup = rsl::tuple; + using Alloc = std::allocator; + using Tag = std::allocator_arg_t; + // TODO : FIXME + // special members + { + static_assert(std::is_default_constructible::value, ""); + static_assert(std::is_move_constructible::value, ""); + static_assert(!std::is_copy_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + } + // args constructors + { + static_assert(std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + static_assert(!std::is_constructible::value, ""); + } + // uses-allocator special member constructors + { + // static_assert(std::is_constructible::value, ""); + // static_assert(std::is_constructible::value, ""); + // static_assert(!std::is_constructible::value, ""); + // static_assert(!std::is_constructible::value, ""); + } + // uses-allocator args constructors + { + // static_assert(std::is_constructible::value, ""); + // static_assert(!std::is_constructible::value, ""); + // static_assert(!std::is_constructible::value, ""); + } +} + +TEST(Tuple, constructor_move) { + { + using T = rsl::tuple<>; + T t0; + T t = std::move(t0); + ((void)t); // Prevent unused warning + } + { + using T = rsl::tuple; + T t0(MoveOnly(0)); + T t = std::move(t0); + ASSERT_EQ(get<0>(t).val, 0); + } + { + using T = rsl::tuple; + T t0(MoveOnly(0), MoveOnly(1)); + T t = std::move(t0); + ASSERT_EQ(get<0>(t).val, 0); + ASSERT_EQ(get<1>(t).val, 1); + } + { + using T = rsl::tuple; + T t0(MoveOnly(0), MoveOnly(1), MoveOnly(2)); + T t = std::move(t0); + ASSERT_EQ(get<0>(t).val, 0); + ASSERT_EQ(get<1>(t).val, 1); + ASSERT_EQ(get<2>(t).val, 2); + } + { + test_sfinae(); + test_sfinae(); + } +} + +TEST(Tuple, constructor_trivially_constructible) { + static_assert(std::is_trivially_constructible>::value, ""); +} + +TEST(Tuple, dtor_trivially_constructible) { + static_assert(!std::is_trivially_destructible::value, ""); + + static_assert(std::is_trivially_destructible>::value, ""); + static_assert(std::is_trivially_destructible>::value, ""); + static_assert(std::is_trivially_destructible>::value, ""); + static_assert(!std::is_trivially_destructible>::value, ""); + static_assert(!std::is_trivially_destructible>::value, ""); + + { + int count = 0; + { + rsl::tuple tuple{TrackDtor(&count)}; + ASSERT_EQ(count, 0); + } + ASSERT_EQ(count, 1); + } +} + +TEST(Tuple, dtor_default) { + { + rsl::tuple<> t; + (void)t; + } + { + rsl::tuple t; + ASSERT_EQ(get<0>(t), 0); + } + { + rsl::tuple t; + ASSERT_EQ(get<0>(t), 0); + ASSERT_EQ(get<1>(t), nullptr); + } + { + rsl::tuple t; + ASSERT_EQ(get<0>(t), 0); + ASSERT_EQ(get<1>(t), nullptr); + ASSERT_EQ(get<2>(t), ""); + } + { + rsl::tuple t; + ASSERT_EQ(get<0>(t), 0); + ASSERT_EQ(get<1>(t), nullptr); + ASSERT_EQ(get<2>(t), ""); + ASSERT_EQ(get<3>(t), DefaultOnly()); + } + { + // See bug #21157. + static_assert(!std::is_default_constructible>(), ""); + static_assert(!std::is_default_constructible>(), ""); + static_assert(!std::is_default_constructible>(), + ""); + } + { + static_assert(noexcept(rsl::tuple()), ""); + static_assert(noexcept(rsl::tuple()), ""); + + static_assert(!noexcept(rsl::tuple()), ""); + static_assert(!noexcept(rsl::tuple()), ""); + static_assert(!noexcept(rsl::tuple()), ""); + } + { + constexpr rsl::tuple<> t; + (void)t; + } + { + constexpr rsl::tuple t{42}; + ASSERT_EQ(get<0>(t), 42); + } + { + constexpr rsl::tuple t{}; + ASSERT_EQ(get<0>(t), 0); + } + { + constexpr rsl::tuple t{}; + ASSERT_EQ(get<0>(t), 0); + ASSERT_EQ(get<1>(t), nullptr); + } + { + // Not supported + // constexpr rsl::tuple t; + // ASSERT_EQ(get<0>(t), 0); + } + { + // Not supported + // constexpr rsl::tuple t; + // ASSERT_EQ(get<0>(t), 0); + // ASSERT_EQ(get<1>(t), nullptr); + } + { + // Check that the SFINAE on the default constructor is not evaluated when + // it isn't needed. If the default constructor is evaluated then this test + // should fail to compile. + IllFormedDefault v(0); + rsl::tuple t(v); + } + { + struct Base {}; + struct Derived : Base { + protected: + Derived() = default; + }; + static_assert(!std::is_default_constructible>::value, ""); + } +} + +TEST(Tuple, constructor_copy) { + { + using T = rsl::tuple<>; + T t0; + T t = t0; + ((void)t); // Prevent unused warning + } + { + using T = rsl::tuple; + T t0(2); + T t = t0; + ASSERT_EQ(get<0>(t), 2); + } + { + using T = rsl::tuple; + T t0(2, 'a'); + T t = t0; + ASSERT_EQ(get<0>(t), 2); + ASSERT_EQ(get<1>(t), 'a'); + } + { + using T = rsl::tuple; + const T t0(2, 'a', "some text"); + T t = t0; + ASSERT_EQ(get<0>(t), 2); + ASSERT_EQ(get<1>(t), 'a'); + ASSERT_EQ(get<2>(t), "some text"); + } + { + using T = rsl::tuple; + constexpr T t0(2); + constexpr T t = t0; + static_assert(get<0>(t) == 2, ""); + } + { + using T = rsl::tuple; + constexpr T t0; + constexpr T t = t0; + constexpr Empty e = get<0>(t); + ((void)e); // Prevent unused warning + } +} + +TEST(Tuple, constructor_convert_non_const_copy) { + // test implicit conversions. + // sizeof...(Types) == 1 + { + rsl::tuple t1{1}; + rsl::tuple> t2 = t1; + ASSERT_EQ(get<0>(t2).v.val, 1); + } + + // test implicit conversions. + // sizeof...(Types) > 1 + { + rsl::tuple t1{1, 2}; + rsl::tuple t2{t1}; + rsl::tuple t3 = t1; + ASSERT_EQ(get<1>(t2), 2); + } + { + rsl::tuple t1{1, 2}; + rsl::tuple, int> t2{t1}; + rsl::tuple, int> t3 = t1; + ASSERT_EQ(get<0>(t2).v.val, 1); + ASSERT_EQ(get<1>(t2), 2); + } + + // test explicit conversions. + // sizeof...(Types) == 1 + { + rsl::tuple t1{1}; + rsl::tuple> t2{t1}; + ASSERT_EQ(get<0>(t2).v.val, 1); + } + + // test explicit conversions. + // sizeof...(Types) > 1 + { + rsl::tuple t1{1, 2}; + rsl::tuple, int> t2{t1}; + ASSERT_EQ(get<0>(t2).v.val, 1); + ASSERT_EQ(get<1>(t2), 2); + } + + // test constraints + + // sizeof...(Types) != sizeof...(UTypes) + static_assert(!std::is_constructible_v, rsl::tuple&>); + static_assert(!std::is_constructible_v, rsl::tuple&>); + static_assert(!std::is_constructible_v, rsl::tuple&>); + + // !(is_constructible_v(FWD(u)))> && ...) + static_assert( + !std::is_constructible_v, rsl::tuple&>); + + // sizeof...(Types) == 1 && other branch of "||" satisfied + { + rsl::tuple t1{}; + rsl::tuple> t2{t1}; + ASSERT_TRUE(non_const_copy_constructor_called(get<0>(t2).v)); + } + + // sizeof...(Types) == 1 && is_same_v + { + rsl::tuple t1{}; + rsl::tuple t2{t1}; + ASSERT_FALSE(non_const_copy_constructor_called(get<0>(t2))); + } + + // sizeof...(Types) != 1 + { + rsl::tuple t1{}; + rsl::tuple t2{t1}; + ASSERT_TRUE(non_const_copy_constructor_called(get<0>(t2))); + } +} + +namespace constructor_convert_move { + +struct B { + int id_; + + explicit B(int i) : id_(i) {} + B(const B&) = default; + B& operator=(const B&) = default; + virtual ~B() {} +}; + +struct D : B { + explicit D(int i) : B(i) {} +}; + +} // namespace constructor_convert_move + +TEST(Tuple, constructor_convert_move) { + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2); + T1 t1 = std::move(t0); + ASSERT_EQ(get<0>(t1), 2); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a'); + T1 t1 = std::move(t0); + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a', constructor_convert_move::D(3)); + T1 t1 = std::move(t0); + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + ASSERT_EQ(get<2>(t1).id_, 3); + } + { + constructor_convert_move::D d(3); + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a', d); + T1 t1 = std::move(t0); + d.id_ = 2; + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + ASSERT_EQ(get<2>(t1).id_, 2); + } + { + using T0 = rsl::tuple>; + using T1 = rsl::tuple>; + T0 t0(2, 'a', std::unique_ptr(new constructor_convert_move::D(3))); + T1 t1 = std::move(t0); + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + ASSERT_EQ(get<2>(t1)->id_, 3); + } + { + rsl::tuple t1(42); + rsl::tuple t2(std::move(t1)); + ASSERT_EQ(get<0>(t2).value, 42); + } + { + rsl::tuple t1(42); + rsl::tuple t2 = std::move(t1); + ASSERT_EQ(get<0>(t2).value, 42); + } +} + +namespace constructor_convert_copy { + +struct B { + int id_; + + explicit B(int i) : id_(i) {} +}; + +struct D : B { + explicit D(int i) : B(i) {} +}; + +struct A { + int id_; + + constexpr A(int i) : id_(i) {} + friend constexpr bool operator==(const A& x, const A& y) { return x.id_ == y.id_; } +}; + +struct C { + int id_; + + constexpr explicit C(int i) : id_(i) {} + friend constexpr bool operator==(const C& x, const C& y) { return x.id_ == y.id_; } +}; + +} // namespace constructor_convert_copy + +TEST(Tuple, constructor_convert_copy) { + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2); + T1 t1 = t0; + ASSERT_EQ(get<0>(t1), 2); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + constexpr T0 t0(2); + constexpr T1 t1 = t0; + static_assert(get<0>(t1) == 2, ""); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + constexpr T0 t0(2); + constexpr T1 t1{t0}; + static_assert(get<0>(t1) == constructor_convert_copy::C(2), ""); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a'); + T1 t1 = t0; + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a', constructor_convert_copy::D(3)); + T1 t1 = t0; + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + ASSERT_EQ(get<2>(t1).id_, 3); + } + { + constructor_convert_copy::D d(3); + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a', d); + T1 t1 = t0; + d.id_ = 2; + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + ASSERT_EQ(get<2>(t1).id_, 2); + } + { + using T0 = rsl::tuple; + using T1 = rsl::tuple; + T0 t0(2, 'a', 3); + T1 t1(t0); + ASSERT_EQ(get<0>(t1), 2); + ASSERT_EQ(get<1>(t1), int('a')); + ASSERT_EQ(get<2>(t1).id_, 3); + } + { + const std::tuple t1(42); + std::tuple t2(t1); + ASSERT_EQ(get<0>(t2).value, 42); + } + { + const std::tuple t1(42); + std::tuple t2 = t1; + ASSERT_EQ(get<0>(t2).value, 42); + } + { + static_assert(std::is_convertible::value, ""); + static_assert( + std::is_convertible&&, const std::tuple&>::value, + ""); + ExplicitTwo e; + std::tuple t = std::tuple(std::move(e)); + ((void)t); + } +} + +TEST(Tuple, constructor_convert_const_move) { + // test implicit conversions. + // sizeof...(Types) == 1 + { + const rsl::tuple t1{1}; + rsl::tuple> t2 = std::move(t1); + ASSERT_EQ(get<0>(t2).v.val, 1); + } + + // test implicit conversions. + // sizeof...(Types) > 1 + { + const rsl::tuple t1{1, 2}; + rsl::tuple, int> t2 = std::move(t1); + ASSERT_EQ(get<0>(t2).v.val, 1); + ASSERT_EQ(get<1>(t2), 2); + } + + // test explicit conversions. + // sizeof...(Types) == 1 + { + const rsl::tuple t1{1}; + rsl::tuple> t2{std::move(t1)}; + ASSERT_EQ(get<0>(t2).v.val, 1); + } + + // test explicit conversions. + // sizeof...(Types) > 1 + { + const rsl::tuple t1{1, 2}; + rsl::tuple, int> t2{std::move(t1)}; + ASSERT_EQ(get<0>(t2).v.val, 1); + ASSERT_EQ(get<1>(t2), 2); + } + + // test constraints + + // sizeof...(Types) != sizeof...(UTypes) + static_assert(!std::is_constructible_v, const rsl::tuple&&>); + static_assert(!std::is_constructible_v, const rsl::tuple&&>); + + // !(is_constructible_v(FWD(u)))> && ...) + static_assert(!std::is_constructible_v, + const rsl::tuple&&>); + + // sizeof...(Types) == 1 && other branch of "||" satisfied + { + const rsl::tuple t1{}; + rsl::tuple> t2{std::move(t1)}; + ASSERT_TRUE(const_move_constructor_called(get<0>(t2).v)); + } + + // sizeof...(Types) == 1 && is_same_v + { + const rsl::tuple t1{}; + rsl::tuple t2{t1}; + ASSERT_TRUE(!const_move_constructor_called(get<0>(t2))); + } + + // sizeof...(Types) != 1 + { + const rsl::tuple t1{}; + rsl::tuple t2{std::move(t1)}; + ASSERT_TRUE(const_move_constructor_called(get<0>(t2))); + } + + // sizeof...(Types) == 1 && is_convertible_v + { + const rsl::tuple t1{}; + rsl::tuple> t2{std::move(t1)}; + ASSERT_TRUE(!const_move_constructor_called(get<0>(t2).v)); + } + + // sizeof...(Types) == 1 && is_constructible_v + { + const rsl::tuple t1{}; + rsl::tuple> t2{std::move(t1)}; + ASSERT_TRUE(!const_move_constructor_called(get<0>(t2).v)); + } +} diff --git a/test/tuple/tuple.get/category.cpp b/test/tuple/tuple.get/category.cpp index 375e676..dfa9e8a 100644 --- a/test/tuple/tuple.get/category.cpp +++ b/test/tuple/tuple.get/category.cpp @@ -4,12 +4,18 @@ #include TEST(Tuple, GetIdxCategory) { - auto tuple = rsl::tuple{42}; - auto const ctuple = rsl::tuple{42}; + auto tuple = rsl::tuple{42}; + auto const ctuple = rsl::tuple{42}; - ASSERT_SAME(decltype(get<0>(tuple)), int&); - ASSERT_SAME(decltype(get<0>(ctuple)), int const&); + ASSERT_SAME(decltype(get<0>(tuple)), int&); + ASSERT_SAME(decltype(get<0>(ctuple)), int const&); - ASSERT_SAME(decltype(get<0>(std::move(tuple))), int&&); - ASSERT_SAME(decltype(get<0>(std::move(ctuple))), int const&&); -} \ No newline at end of file + ASSERT_SAME(decltype(get<0>(std::move(tuple))), int&&); + ASSERT_SAME(decltype(get<0>(std::move(ctuple))), int const&&); + + ASSERT_SAME(decltype(tuple.template get<0>()), int&); + ASSERT_SAME(decltype(ctuple.template get<0>()), int const&); + + ASSERT_SAME(decltype(std::move(tuple).template get<0>()), int&&); + ASSERT_SAME(decltype(std::move(ctuple).template get<0>()), int const&&); +}