diff --git a/CHANGELOG.md b/CHANGELOG.md index b11c3bb..c829069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.2.35 + +* Added `mbo::types::Required` which is similar to `RefWrap` but stores the actual type (and unlike `std::optional` cannot be reset). + # 0.2.34 * Added `mbo::testing::WhenTransformedBy` which allows to compare containers after transforming them. diff --git a/README.md b/README.md index 4e23c7b..4007dff 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,9 @@ The C++ library is organized in functional groups each residing in their own dir * Mainly, this prevents calling the destructor and thus prevents termination issues (initialization order fiasco). * mbo/types:ref_wrap_cc, mbo/types/ref_wrap.h * template-type `RefWrap`: similar to `std::reference_wrapper` but supports operators `->` and `*`. + * mbo/types:required_cc, mbo/types/required.h + * template-type `Required`: similar to `RefWrap` but stores the actual type (and unlike `std::optional` cannot be reset). + * mbo/types:template_search_cc, mbo/types/template_search.h: * template struct `BinarySearch` implements templated binary search algorithm. * template struct `LinearSearch` implements templated linear search algorithm. diff --git a/mbo/testing/matchers.h b/mbo/testing/matchers.h index cd92c58..ed8c2d6 100644 --- a/mbo/testing/matchers.h +++ b/mbo/testing/matchers.h @@ -16,7 +16,7 @@ #ifndef MBO_TESTING_MATCHERS_H_ #define MBO_TESTING_MATCHERS_H_ -#include +#include // IWYU pragma: keep #include #include "gmock/gmock-matchers.h" diff --git a/mbo/types/BUILD.bazel b/mbo/types/BUILD.bazel index 6e09866..bc8fa44 100644 --- a/mbo/types/BUILD.bazel +++ b/mbo/types/BUILD.bazel @@ -137,6 +137,21 @@ cc_test( ], ) +cc_library( + name = "required_cc", + hdrs = ["required.h"], + visibility = ["//visibility:public"], +) + +cc_test( + name = "required_test", + srcs = ["required_test.cc"], + deps = [ + ":required_cc", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "stringify_cc", srcs = ["stringify.cc"], diff --git a/mbo/types/required.h b/mbo/types/required.h new file mode 100644 index 0000000..6fb9979 --- /dev/null +++ b/mbo/types/required.h @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright (c) The helly25/mbo authors (helly25.com) +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MBO_TYPES_REQUIRED_H_ +#define MBO_TYPES_REQUIRED_H_ + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include + +namespace mbo::types { + +// Template class `Required` is a wrapper around a type `T`. The value can be +// replaced without using assign or move-assign operators. Instead the wrapper +// will call the destructor and then in-place construct the wrapper value using +// the type's move constructor. +// +// In other words the reson for this type is he rare case where a type `T` that +// follows the rule of 0/3/5 and of the 4 ctor/assignments only allows for +// move-construction all while (say a test) needs to be implemented with assign. +// +// The type is similar to `std::optional` only it cannot be `std::nullopt`. +// The type is however closer to `RefWrap`. +// +// The wrapper is not implemented itself as a wrapper or specialization of STL +// type `std::optional` due to its overhead. +template +class Required { + public: + using type = T; + + Required() = default; + + constexpr explicit Required(T v) : value_(std::move(v)) {} + + template + requires(std::constructible_from && !std::same_as) + constexpr explicit Required(U&& v) : value_(std::forward(v)) {} + + template + requires(std::constructible_from) + constexpr explicit Required(std::in_place_t, Args&&... args) : value_(std::forward(args)...) {} + + constexpr Required& emplace(T v) { // NOLINT(readability-identifier-naming) + value_.~T(); + new (&value_) T(std::move(v)); + return *this; + } + + template + requires(std::constructible_from) + constexpr Required& emplace(Args&&... args) { // NOLINT(readability-identifier-naming) + value_.~T(); + new (&value_) T(std::forward(args)...); + return *this; + } + + constexpr T& get() noexcept { return value_; } // NOLINT(*-identifier-naming) + + constexpr const T& get() const noexcept { return value_; } // NOLINT(*-identifier-naming) + + constexpr T* operator->() noexcept __attribute__((returns_nonnull)) { return &value_; } + + constexpr const T* operator->() const noexcept __attribute__((returns_nonnull)) { return &value_; } + + constexpr T& operator*() noexcept { return value_; } + + constexpr const T& operator*() const noexcept { return value_; } + + constexpr operator T&() noexcept { return value_; } // NOLINT(*-explicit-*) + + constexpr operator const T &() const noexcept { return value_; } // NOLINT(*-explicit-*) + + template U> + constexpr auto operator<=>(const Required& other) const noexcept { + if (value_ == other) { + return decltype(value_ <=> other)::equivalent; + } + return value_ <=> other.value_; + } + + template U> + constexpr auto operator<=>(const U& other) const noexcept { + if (value_ == other) { + return decltype(value_ <=> other)::equivalent; + } + return value_ <=> other; + } + + private: + T value_; +}; + +} // namespace mbo::types + +#endif // MBO_TYPES_REQUIRED_H_ diff --git a/mbo/types/required_test.cc b/mbo/types/required_test.cc new file mode 100644 index 0000000..29ae64e --- /dev/null +++ b/mbo/types/required_test.cc @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright (c) The helly25/mbo authors (helly25.com) +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "mbo/types/required.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace mbo::types { +namespace { + +// NOLINTBEGIN(*-magic-numbers) + +using ::testing::Ge; +using ::testing::IsEmpty; +using ::testing::Le; +using ::testing::Not; +using ::testing::Pair; + +struct RequiredTest : ::testing::Test {}; + +TEST_F(RequiredTest, Basics) { + Required req(25); + ASSERT_THAT(req, 25); + req.emplace(33); + EXPECT_THAT(req, 33); +} + +TEST_F(RequiredTest, Compare) { + Required req(25); + EXPECT_THAT(req, 25); + EXPECT_THAT(req <= 55, true); + EXPECT_THAT(req < 55, true); + EXPECT_THAT(req <= 11, false); + EXPECT_THAT(req < 11, false); + EXPECT_THAT(req == 55, false); + EXPECT_THAT(req != 55, true); + EXPECT_THAT(req == 25, true); + EXPECT_THAT(req != 25, false); + EXPECT_THAT(55 >= req, true); + EXPECT_THAT(55 > req, true); + EXPECT_THAT(11 >= req, false); + EXPECT_THAT(11 > req, false); + EXPECT_THAT(req, Le(55)); + EXPECT_THAT(req, Le(25)); + EXPECT_THAT(req, Not(Le(11))); + EXPECT_THAT(req, Ge(11)); + EXPECT_THAT(req, Ge(25)); + EXPECT_THAT(req, Not(Ge(33))); + int val = 25; + Required cmp(val); + EXPECT_THAT(req == val, true); + EXPECT_THAT(req != val, false); + EXPECT_THAT(req <= val, true); + EXPECT_THAT(req >= val, true); + EXPECT_THAT(req < val, false); + EXPECT_THAT(req > val, false); + val = 11; + EXPECT_THAT(req == val, false); + EXPECT_THAT(req != val, true); + EXPECT_THAT(req <= val, false); + EXPECT_THAT(req >= val, true); + EXPECT_THAT(req < val, false); + EXPECT_THAT(req > val, true); + val = 33; + EXPECT_THAT(req == val, false); + EXPECT_THAT(req != val, true); + EXPECT_THAT(req <= val, true); + EXPECT_THAT(req >= val, false); + EXPECT_THAT(req < val, true); + EXPECT_THAT(req > val, false); +} + +TEST_F(RequiredTest, Pair) { + Required> req({25, 33}); + EXPECT_THAT(*req, Pair(25, 33)); + EXPECT_THAT(req->first, 25); + EXPECT_THAT(req->second, 33); + req.emplace({42, 99}); + EXPECT_THAT(*req, Pair(42, 99)); + EXPECT_THAT(req->first, 42); + EXPECT_THAT(req->second, 99); +} + +TEST_F(RequiredTest, PairByArgs) { + Required> req(std::in_place, 25, 33); + EXPECT_THAT(*req, Pair(25, 33)); + EXPECT_THAT(req->first, 25); + EXPECT_THAT(req->second, 33); + req.emplace(42, 99); + EXPECT_THAT(*req, Pair(42, 99)); + EXPECT_THAT(req->first, 42); + EXPECT_THAT(req->second, 99); +} + +struct NoDefCtor { + NoDefCtor() = delete; + + explicit NoDefCtor(int v) : value(v) {} + + bool operator==(int v) const { return v == value; } + + int value; +}; + +TEST_F(RequiredTest, NoDefCtor) { + Required req(25); + EXPECT_THAT(*req, 25); +} + +TEST_F(RequiredTest, DefCtor) { + Required req; + EXPECT_THAT(*req, IsEmpty()); +} + +// NOLINTEND(*-magic-numbers) + +} // namespace +} // namespace mbo::types