Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type Required #78

Merged
merged 3 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`: similar to `std::reference_wrapper` but supports operators `->` and `*`.
* mbo/types:required_cc, mbo/types/required.h
* template-type `Required<T>`: 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.
Expand Down
2 changes: 1 addition & 1 deletion mbo/testing/matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#ifndef MBO_TESTING_MATCHERS_H_
#define MBO_TESTING_MATCHERS_H_

#include <concepts>
#include <concepts> // IWYU pragma: keep
#include <type_traits>

#include "gmock/gmock-matchers.h"
Expand Down
15 changes: 15 additions & 0 deletions mbo/types/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
108 changes: 108 additions & 0 deletions mbo/types/required.h
Original file line number Diff line number Diff line change
@@ -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 <compare> // IWYU pragma: keep
#include <concepts> // IWYU pragma: keep
#include <utility>

namespace mbo::types {

// Template class `Required<T>` 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<typename T>
class Required {
public:
using type = T;

Required() = default;

constexpr explicit Required(T v) : value_(std::move(v)) {}

template<typename U>
requires(std::constructible_from<T, U> && !std::same_as<T, U>)
constexpr explicit Required(U&& v) : value_(std::forward<U>(v)) {}

template<typename... Args>
requires(std::constructible_from<T, Args...>)
constexpr explicit Required(std::in_place_t, Args&&... args) : value_(std::forward<Args>(args)...) {}

constexpr Required& emplace(T v) { // NOLINT(readability-identifier-naming)
value_.~T();
new (&value_) T(std::move(v));
return *this;
}

template<typename... Args>
requires(std::constructible_from<T, Args...>)
constexpr Required& emplace(Args&&... args) { // NOLINT(readability-identifier-naming)
value_.~T();
new (&value_) T(std::forward<Args>(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<std::three_way_comparable_with<T> U>
constexpr auto operator<=>(const Required<U>& other) const noexcept {
if (value_ == other) {
return decltype(value_ <=> other)::equivalent;
}
return value_ <=> other.value_;
}

template<std::three_way_comparable_with<T> 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_
134 changes: 134 additions & 0 deletions mbo/types/required_test.cc
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <utility>

#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<int> req(25);
ASSERT_THAT(req, 25);
req.emplace(33);
EXPECT_THAT(req, 33);
}

TEST_F(RequiredTest, Compare) {
Required<const int> 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<int> 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<std::pair<int, int>> 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<std::pair<int, int>> 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<NoDefCtor> req(25);
EXPECT_THAT(*req, 25);
}

TEST_F(RequiredTest, DefCtor) {
Required<std::string> req;
EXPECT_THAT(*req, IsEmpty());
}

// NOLINTEND(*-magic-numbers)

} // namespace
} // namespace mbo::types
Loading