diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ff18a80 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +--- +BasedOnStyle: Google +--- +Language: Cpp +DerivePointerAlignment: false +PointerAlignment: Left diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..94ee531 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright 2019 Google LLC +# +# 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 +# +# https://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. +cmake_minimum_required(VERSION 3.13) + +enable_testing() +find_package(GTest MODULE REQUIRED) + +# Include directories accessible from here. +include_directories(.) + +# Require C++17 with no extensions. +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Add the test to the std::expected implementation. +add_executable(expected_test netlib/expected_test.cc) +target_link_libraries(expected_test PRIVATE GTest::GTest GTest::Main) +add_test(expected_test expected_test) diff --git a/netlib/README.md b/netlib/README.md new file mode 100644 index 0000000..46f9243 --- /dev/null +++ b/netlib/README.md @@ -0,0 +1,29 @@ +# netlib Directory + +This is the main source directory for the project. The intent is to keep this +directory flat with subdirectories for logical groupings. This means all the +headers, implementation, and test code should be co-hosted in this directory. + +## Structure + +All implementation files must end with the `.cc` filename extension, and all +headers must end in `.h`. If we have a file named `connection.cc` the header +must be `connection.h` and the test(s) should be in `connection_test.cc`. + +We shall control the installed headers through our CMake configuration +instead of assuming that all headers are publicly accessible. When including +files, we should always include headers in the netlib repository by relative +inclusion with the `netlib/` directory (based off the root of the +repository). As an example: + +```c++ +// In connection.cc and connection_test.cc. +#include "netlib/connection.h" +``` + +## Subdirectories + +We can introduce subdirectories for logical grouping, each one following the +same structure rules as described here. For instance, if we have a +subdirectory of encoding/decoding, we can introduce a `coding/` subdirectory +with all the encoders and decoders implemented. \ No newline at end of file diff --git a/netlib/expected.h b/netlib/expected.h new file mode 100644 index 0000000..514b3f3 --- /dev/null +++ b/netlib/expected.h @@ -0,0 +1,474 @@ +// Copyright 2019 Google LLC +// +// 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 +// +// https://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 CPP_NETLIB_EXPECTED_H_ +#define CPP_NETLIB_EXPECTED_H_ + +#include +#include +#include + +namespace cppnetlib { + +/// This is a minimal implementation of the proposed P0323R3 +/// std::unexpected<...> API. +template +// requires EqualityComparable && (CopyConstructible || +// MoveConstructible) +class unexpected { + public: + static_assert(!std::is_same_v, "unexpected is not supported."); + + unexpected() = delete; + constexpr explicit unexpected(const E& e) : value_(e) {} + constexpr explicit unexpected(E&& e) : value_(std::move(e)) {} + constexpr const E& value() const& { return value_; } + constexpr E& value() & { return value_; } + constexpr E&& value() && { return std::move(value_); } + constexpr const E&& value() const&& { return std::move(value_); } + + // This is a deviation from the proposal which suggests that these functions + // are namespace-level functions, instead of ADL-only found comparison + // operators (defined friend free functions). + template + friend constexpr bool operator==(const unexpected& lhs, + const unexpected& rhs) { + return lhs.value_ == rhs.value_; + } + + template + friend constexpr bool operator!=(const unexpected& lhs, + const unexpected& rhs) { + return lhs.value_ == rhs.value_; + } + + private: + E value_; +}; + +struct unexpect_t { + unexpect_t() = default; +}; +inline constexpr unexpect_t unexpect{}; + +template +class bad_expected_access; + +template <> +class bad_expected_access { + public: + virtual const char* what() const noexcept { + return "cppnetlib::bad_expected_access"; + } + virtual ~bad_expected_access() {} +}; + +template +class bad_expected_access : public bad_expected_access { + public: + explicit bad_expected_access(E e) : val_(e) {} + const char* what() const noexcept override { + return "cppnetlib::bad_expected_access"; + } + const E& error() const&; + E& error() &; + E&& error() &&; + + private: + E val_; +}; + +/// This is a minimal implementation of the proposed P0323R3 +/// std::expected<...> API. +// TODO: Implement a specilisation of expected. +// TODO: Implement more member functions as needed. +template +class [[nodiscard]] expected { + public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + + template + struct rebind { + using type = expected; + }; + + constexpr expected() : has_value_(false) { + new (&union_storage_) T(); + has_value_ = true; + } + constexpr expected(const expected& other) + : union_storage_(other.union_storage_), has_value_(other.has_value_) {} + + constexpr expected(expected && other, + std::enable_if_t < std::is_move_constructible_v && + std::is_move_constructible_v> * = + 0) noexcept(std::is_nothrow_move_constructible_v && + std::is_nothrow_move_constructible_v) + : has_value_(other.has_value_) { + if (other.has_value_) + new (union_storage_) + T(std::move(*reinterpret_cast(other.union_storage_))); + else + new (union_storage_) + unexpected(std::move(*reinterpret_cast(other.union_storage_))); + } + + private: + template + using constructor_helper_t = + std::enable_if_t and + std::is_constructible_v and + !std::is_constructible_v&> and + !std::is_constructible_v&&> and + !std::is_constructible_v&> and + !std::is_constructible_v&&> and + !std::is_convertible_v&, T> and + !std::is_convertible_v&&, T> and + !std::is_convertible_v&, T> and + !std::is_convertible_v&&, T>>; + + public: + // TODO: c++20 has support for conditional explicit, use that instead of the + // duplication. + template and + std::is_convertible_v), + bool> = false> + explicit constexpr expected(const expected& other, + constructor_helper_t* = 0) + : has_value_(other.has_value_) { + if (other.has_value_) + new (&union_storage_) T(*other); + else + new (&union_storage_) unexpected(other.error()); + } + + template and + std::is_convertible_v), + bool> = false> + constexpr expected(const expected& other, + constructor_helper_t* = 0) + : has_value_(other.has_value_) { + if (other.has_value_) + new (&union_storage_) T(*other); + else + new (&union_storage_) unexpected(other.error()); + } + + template and + std::is_convertible_v), + bool> = false> + explicit constexpr expected(const expected&& other, + constructor_helper_t* = 0) + : has_value_(other.has_value_) { + if (other.has_value_) + new (&union_storage_) T(std::move(*other)); + else + new (&union_storage_) unexpected(std::move(unexpected(other.error()))); + } + + template , bool> = false> + explicit constexpr expected( + U && value, + std::enable_if_t and + !std::is_same_v, std::in_place_t> and + !std::is_same_v, std::decay_t> and + !std::is_same_v, std::decay_t>>* = 0) + : has_value_(true) { + new (&union_storage_) U(std::forward(std::move(value))); + } + + template , bool> = false> + constexpr expected( + U && value, + std::enable_if_t and + !std::is_same_v, std::in_place_t> and + !std::is_same_v, std::decay_t> and + !std::is_same_v, std::decay_t>>* = 0) + : has_value_(true) { + new (&union_storage_) U(std::forward(value)); + } + + template , bool> = false> + explicit constexpr expected(unexpected const& e) : has_value_(false) { + new (&union_storage_) unexpected(e); + } + + template , bool> = false> + constexpr expected(unexpected const& e) : has_value_(false) { + new (&union_storage_) unexpected(e); + } + + template , bool> = false> + explicit constexpr expected(unexpected && e) noexcept( + std::is_nothrow_move_constructible_v) + : has_value_(false) { + new (&union_storage_) unexpected(std::move(e.value())); + } + + template , bool> = false> + constexpr expected(unexpected && + e) noexcept(std::is_nothrow_constructible_v) + : has_value_(false) { + new (&union_storage_) unexpected(std::move(e.value())); + } + + // Destructor. + ~expected() { + if constexpr (!std::is_trivially_destructible_v) { + if (has_value_) + reinterpret_cast(&union_storage_)->~T(); + else + reinterpret_cast(&union_storage_)->~unexpected_type(); + } + } + + // Assignment. + expected& operator=(const expected& other) { + if (has_value_ and other.has_value_) { + **this = *other; + return *this; + } + + if (!has_value_ and !other.has_value_) { + (*reinterpret_cast(&union_storage_)) = + unexpected(other.error()); + return *this; + } + + if (has_value_ and !other.has_value_) { + reinterpret_cast(&union_storage_)->~T(); + new (&union_storage_) unexpected_type(unexpected(other.error())); + has_value_ = false; + return *this; + } + + assert(!has_value_ and other.has_value_); + if constexpr (std::is_nothrow_copy_constructible_v) { + reinterpret_cast(&union_storage_)->~unexpected_type(); + new (&union_storage_) T(other.value()); + has_value_ = true; + } else if constexpr (std::is_nothrow_move_constructible_v) { + T tmp = *other; + reinterpret_cast(&union_storage_)->~unexpected_type(); + new (&union_storage_) T(tmp); + has_value_ = true; + } else if constexpr (std::is_nothrow_move_constructible_v) { + unexpected_type tmp = unexpected(std::move(error())); + reinterpret_cast(&union_storage_)->~unexpected_type(); + try { + new (&union_storage_) T(*other); + has_value_ = true; + } catch (...) { + new (&union_storage_) unexpected_type(std::move(tmp)); + throw; + } + } + + return *this; + } + + expected& operator=(expected&& other) noexcept( + std::is_nothrow_move_assignable_v and + std::is_nothrow_move_constructible_v) { + if (has_value_ and other.has_value_) { + **this = std::move(*other); + return *this; + } + + if (!has_value_ and !other.has_value_) { + (*reinterpret_cast(&union_storage_)) = + unexpected(std::move(other.error())); + return *this; + } + + if (has_value_ and !other.has_value_) { + reinterpret_cast(&union_storage_)->~T(); + new (&union_storage_) unexpected_type( + std::move(std::forward>(other).error())); + has_value_ = false; + return *this; + } + + assert(!has_value_ and other.has_value_); + if constexpr (std::is_nothrow_move_constructible_v) { + reinterpret_cast(&union_storage_)->~T(); + new (&union_storage_) T(*std::move(other)); + has_value_ = true; + } else if constexpr (std::is_nothrow_move_constructible_v()) { + unexpected_type tmp = unexpected(std::move(error())); + reinterpret_cast(&union_storage_)->~unexpected_type(); + try { + new (&union_storage_) T(*std::move(other)); + has_value_ = true; + } catch (...) { + new (&union_storage_) unexpected_type(std::move(tmp)); + throw; + } + } + + return *this; + } + + template and + std::is_assignable_v, + bool> = false> + expected& operator=(const unexpected& e) noexcept( + std::is_nothrow_copy_assignable_v and + std::is_nothrow_copy_constructible_v) { + if (!has_value_) { + (*reinterpret_cast(&union_storage_)) = e; + return *this; + } + + reinterpret_cast(&union_storage_)->~T(); + new (&union_storage_) + unexpected_type(unexpected(std::forward(e))); + has_value_ = false; + return *this; + } + + template and + std::is_nothrow_move_assignable_v, + bool> = false> + expected& operator=(unexpected&& e) noexcept( + std::is_nothrow_move_assignable_v) { + if (!has_value_) { + (*reinterpret_cast(&union_storage_)) = + unexpected(std::move(std::forward>(e))); + return *this; + } + + reinterpret_cast(&union_storage_)->~T(); + new (&union_storage_) + unexpected_type(unexpected(std::forward(e))); + has_value_ = false; + return *this; + } + + template < + class U, + std::enable_if_t< + !std::is_same_v, std::decay_t> and + !std::conjunction_v, + std::is_same>> and + std::is_constructible_v and std::is_assignable_v and + std::is_nothrow_move_constructible_v, + bool> = false> + expected& operator=(U&& value) { + if (has_value_) { + this->value() = std::forward(value); + return *this; + } + + if constexpr (std::is_nothrow_constructible_v) { + reinterpret_cast(&union_storage_)->~unexpected_type(); + new (&union_storage_) T(std::forward(value)); + has_value_ = true; + } else if constexpr (std::is_nothrow_constructible_v) { + unexpected_type tmp(std::move(error())); + reinterpret_cast(&union_storage_)->~unexpected_type(); + try { + new (&union_storage_) T(std::forward(value)); + has_value_ = true; + } catch (...) { + new (&union_storage_) unexpected_type(std::move(tmp)); + throw; + } + } + return *this; + } + + // Observer functions. + constexpr const T* operator->() const { + if (!has_value_) throw bad_expected_access(error()); + return reinterpret_cast(&union_storage_); + } + + constexpr T* operator->() { + if (!has_value_) throw bad_expected_access(error()); + return reinterpret_cast(&union_storage_); + } + + constexpr const T& operator*() const& { + if (!has_value_) throw bad_expected_access(error()); + return *reinterpret_cast(&union_storage_); + } + + constexpr T& operator*()& { + if (!has_value_) throw bad_expected_access(error()); + return *reinterpret_cast(&union_storage_); + } + + constexpr const T&& operator*() const&& { + if (!has_value_) throw bad_expected_access(error()); + return std::move(*reinterpret_cast(&union_storage_)); + } + + constexpr T&& operator*()&& { + if (!has_value_) throw bad_expected_access(error()); + return std::move(*reinterpret_cast(&union_storage_)); + } + + constexpr explicit operator bool() const noexcept { return has_value_; } + constexpr bool has_value() const noexcept { return has_value_; } + + constexpr const T& value() const& { return **this; } + constexpr T& value()& { return **this; } + constexpr const T&& value() const&& { return std::move(**this); } + constexpr T&& value()&& { return std::move(**this); } + + constexpr const E& error() const& { + assert(!has_value_ && + "expected must not have a value when taking an error!"); + return reinterpret_cast*>(&union_storage_)->value(); + } + + constexpr E& error()& { + assert(!has_value_ && + "expected must not have a value when taking an error!"); + return reinterpret_cast*>(&union_storage_)->value(); + } + + constexpr const E&& error() const&& { + assert(!has_value_ && + "expected must not have a value when taking an error!"); + return std::move(*reinterpret_cast*>(&union_storage_)) + ->value(); + } + + constexpr E&& error()&& { + assert(!has_value_ && + "expected must not have a value when taking an error!"); + return std::move(*reinterpret_cast*>(&union_storage_)) + .value(); + }; + + private: + std::aligned_union_t<8, value_type, unexpected_type> union_storage_; + bool has_value_; +}; + +} // namespace cppnetlib + +#endif // CPP_NETLIB_EXPECTED_H_ diff --git a/netlib/expected_test.cc b/netlib/expected_test.cc new file mode 100644 index 0000000..8e0f14e --- /dev/null +++ b/netlib/expected_test.cc @@ -0,0 +1,102 @@ +// Copyright 2019 Google LLC +// +// 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 +// +// https://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 "netlib/expected.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace cppnetlib { +namespace { + +using ::testing::Eq; + +using error_code = int; + +enum errors : error_code { undefined }; + +expected f(bool b) { + if (b) return true; + return unexpected(errors::undefined); +} + +// For testing support for larger types. +struct test_data { + int64_t first; + int64_t second; +}; + +expected g(bool b, int64_t first, int64_t second) { + if (b) return unexpected{errors::undefined}; + return test_data{first, second}; +} + +TEST(ExpectedTest, Construction) { expected e; } + +TEST(ExpectedTest, Unexpected) { + auto e1 = f(true); + ASSERT_TRUE(e1); + + auto e2 = f(false); + ASSERT_FALSE(e2); + ASSERT_THROW(*e2, bad_expected_access); + + auto e3 = g(true, 1, 2); + ASSERT_FALSE(e3); + EXPECT_THAT(e3.error(), Eq(errors::undefined)); + ASSERT_THROW(*e3, bad_expected_access); + + e3 = g(false, 2, 3); + ASSERT_TRUE(e3); + ASSERT_THAT(e3->first, Eq(2)); + ASSERT_THAT(e3->second, Eq(3)); +} + +TEST(ExpectedTest, AssignmentSimple) { + expected e; + ASSERT_NO_THROW(e = 1); + EXPECT_THAT(e.value(), Eq(1)); + ASSERT_NO_THROW(e = {}); + EXPECT_THAT(e.value(), Eq(int{})); + ASSERT_NO_THROW(e = unexpected{errors::undefined}); + ASSERT_THROW(*e, bad_expected_access); + EXPECT_THAT(e.error(), Eq(errors::undefined)); +} + +struct throwing_copy_exception : public std::exception { + const char* what() const noexcept override { + return "throwing copy exception"; + } +}; + +struct throwing_copy { + throwing_copy() = default; + throwing_copy(const throwing_copy& e) { throw throwing_copy_exception(); } + throwing_copy& operator=(const throwing_copy&) { + throw throwing_copy_exception(); + } + throwing_copy(throwing_copy&&) = default; + throwing_copy& operator=(throwing_copy&&) = default; + ~throwing_copy() = default; +}; + +TEST(ExpectedTest, AssignmentThrowingCopy) { + expected instance; + ASSERT_NO_THROW(instance = throwing_copy{}); + throwing_copy tmp; + ASSERT_NO_THROW(instance = std::move(tmp)); + ASSERT_THROW(instance = tmp, throwing_copy_exception); + ASSERT_THROW(*instance = tmp, throwing_copy_exception); +} + +} // namespace +} // namespace cppnetlib