Skip to content

Commit d3fffac

Browse files
committed
Work In Progress: A minimal std::expected<T, E>
This change includes a minimal implementation of the std::expected<T, E> proposal in P0323R3 (http://wg21.link/P0323R3) which is hosted in the `cppnetlib` repository. In the process we also document some of our guidelines on the structure of the repository, on naming files, directory structure, and dependency requirements. Still TODO: * Fill out the test suite. * Update documentation on using vcpkg on dependencies. * Work our way up to a minimal interface for a connecton type, using our std::expected utility type. This is related to the work in netlib/cpp-netlib#5 for implementing a minimal connection type suitable as a building block for higher level protocol implementations.
1 parent a8b0aac commit d3fffac

File tree

5 files changed

+321
-0
lines changed

5 files changed

+321
-0
lines changed

Diff for: .clang-format

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
BasedOnStyle: Google
3+
---
4+
Language: Cpp
5+
DerivePointerAlignment: false
6+
PointerAlignment: Left

Diff for: CMakeLists.txt

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
cmake_minimum_required(VERSION 3.13)
15+
16+
enable_testing()
17+
find_package(GTest MODULE REQUIRED)
18+
19+
# Include directories accessible from here.
20+
include_directories(.)
21+
22+
# Require C++17 with no extensions.
23+
set(CMAKE_CXX_STANDARD 17)
24+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
25+
set(CMAKE_CXX_EXTENSIONS OFF)
26+
27+
# Add the test to the std::expected implementation.
28+
add_executable(expected_test netlib/expected_test.cc)
29+
target_link_libraries(expected_test PRIVATE GTest::GTest GTest::Main)
30+
add_test(expected_test expected_test)

Diff for: netlib/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# netlib Directory
2+
3+
This is the main source directory for the project. The intent is to keep this
4+
directory flat with subdirectories for logical groupings. This means all the
5+
headers, implementation, and test code should be co-hosted in this directory.
6+
7+
## Structure
8+
9+
All implementation files must end with the `.cc` filename extension, and all
10+
headers must end in `.h`. If we have a file named `connection.cc` the header
11+
must be `connection.h` and the test(s) should be in `connection_test.cc`.
12+
13+
We shall control the installed headers through our CMake configuration
14+
instead of assuming that all headers are publicly accessible. When including
15+
files, we should always include headers in the netlib repository by relative
16+
inclusion with the `netlib/` directory (based off the root of the
17+
repository). As an example:
18+
19+
```c++
20+
// In connection.cc and connection_test.cc.
21+
#include "netlib/connection.h"
22+
```
23+
24+
## Subdirectories
25+
26+
We can introduce subdirectories for logical grouping, each one following the
27+
same structure rules as described here. For instance, if we have a
28+
subdirectory of encoding/decoding, we can introduce a `coding/` subdirectory
29+
with all the encoders and decoders implemented.

Diff for: netlib/expected.h

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
#ifndef CPP_NETLIB_EXPECTED_H_
15+
#define CPP_NETLIB_EXPECTED_H_
16+
17+
#include <type_traits>
18+
#include <utility>
19+
20+
namespace cppnetlib {
21+
22+
/// This is a minimal implementation of the proposed P0323R3
23+
/// std::unexpected<...> API.
24+
template <class E>
25+
// requires EqualityComparable<E> && (CopyConstructible<E> ||
26+
// MoveConstructible<E>)
27+
class unexpected {
28+
public:
29+
static_assert(!std::is_same_v<E, void>, "unexpected<void> is not supported.");
30+
31+
unexpected() = delete;
32+
constexpr explicit unexpected(const E& e) : value_(e) {}
33+
constexpr explicit unexpected(E&& e) : value_(std::move(e)) {}
34+
constexpr const E& value() const& { return value_; }
35+
constexpr E& value() & { return value_; }
36+
constexpr E&& value() && { return std::move(value_); }
37+
constexpr const E&& value() const&& { return std::move(value_); }
38+
39+
// This is a deviation from the proposal which suggests that these functions
40+
// are namespace-level functions, instead of ADL-only found comparison
41+
// operators (defined friend free functions).
42+
template <class F>
43+
friend constexpr bool operator==(const unexpected<F>& lhs,
44+
const unexpected<F>& rhs) {
45+
return lhs.value_ == rhs.value_;
46+
}
47+
48+
template <class F>
49+
friend constexpr bool operator!=(const unexpected<F>& lhs,
50+
const unexpected<F>& rhs) {
51+
return lhs.value_ == rhs.value_;
52+
}
53+
54+
private:
55+
E value_;
56+
};
57+
58+
struct unexpect_t {
59+
unexpect_t() = default;
60+
};
61+
inline constexpr unexpect_t unexpect{};
62+
63+
template <class E>
64+
class bad_expected_access {};
65+
66+
/// This is a minimal implementation of the proposed P0323R3
67+
/// std::expected<...> API.
68+
template <class T, class E>
69+
class expected {
70+
public:
71+
using value_type = T;
72+
using error_type = E;
73+
using unexpected_type = unexpected<E>;
74+
75+
template <class U>
76+
struct rebind {
77+
using type = expected<U, error_type>;
78+
};
79+
80+
constexpr expected() : has_value_(false) {}
81+
constexpr expected(const expected& other)
82+
: union_storage_(other.union_storage_), has_value_(other.has_value_) {}
83+
84+
constexpr expected(
85+
expected&& other,
86+
std::enable_if_t<std::is_move_constructible_v<T> &&
87+
std::is_move_constructible_v<E>>* =
88+
0) noexcept(std::is_nothrow_move_constructible_v<T>&&
89+
std::is_nothrow_move_constructible_v<E>)
90+
: has_value_(other.has_value_) {
91+
if (other.has_value_)
92+
new (union_storage_)
93+
T(std::move(*reinterpret_cast<T*>(other.union_storage_)));
94+
else
95+
new (union_storage_)
96+
unexpected<E>(std::move(*reinterpret_cast<E*>(other.union_storage_)));
97+
}
98+
99+
private:
100+
template <class U, class G>
101+
using constructor_helper_t =
102+
std::enable_if_t<std::is_constructible_v<T, const U&> and
103+
std::is_constructible_v<E, const G&> and
104+
!std::is_constructible_v<T, expected<U, G>&> and
105+
!std::is_constructible_v<T, expected<U, G>&&> and
106+
!std::is_constructible_v<T, const expected<U, G>&> and
107+
!std::is_constructible_v<T, const expected<U, G>&&> and
108+
!std::is_convertible_v<expected<U, G>&, T> and
109+
!std::is_convertible_v<expected<U, G>&&, T> and
110+
!std::is_convertible_v<const expected<U, G>&, T> and
111+
!std::is_convertible_v<const expected<U, G>&&, T>>;
112+
113+
public:
114+
// TODO: c++20 has support for conditional explicit, use that instead of the
115+
// duplication.
116+
template <class U, class G,
117+
std::enable_if_t<!(std::is_convertible_v<const U&, T> and
118+
std::is_convertible_v<const G&, E>),
119+
bool> = false>
120+
explicit constexpr expected(const expected<U, G>& other,
121+
constructor_helper_t<U, G>* = 0)
122+
: has_value_(other.has_value_) {
123+
if (other.has_value_)
124+
new (&union_storage_) T(*other);
125+
else
126+
new (&union_storage_) unexpected<E>(other.error());
127+
}
128+
129+
template <class U, class G,
130+
std::enable_if_t<(std::is_convertible_v<const U&, T> and
131+
std::is_convertible_v<const G&, E>),
132+
bool> = false>
133+
constexpr expected(const expected<U, G>& other,
134+
constructor_helper_t<U, G>* = 0)
135+
: has_value_(other.has_value_) {
136+
if (other.has_value_)
137+
new (&union_storage_) T(*other);
138+
else
139+
new (&union_storage_) unexpected<E>(other.error());
140+
}
141+
142+
template <class U, class G,
143+
std::enable_if_t<!(std::is_convertible_v<const U&, T> and
144+
std::is_convertible_v<const G&, E>),
145+
bool> = false>
146+
explicit constexpr expected(const expected<U, G>&& other,
147+
constructor_helper_t<U, G>* = 0)
148+
: has_value_(other.has_value_) {
149+
if (other.has_value_)
150+
new (&union_storage_) T(std::move(*other));
151+
else
152+
new (&union_storage_) unexpected<E>(std::move(unexpected(other.error())));
153+
}
154+
155+
template <class U = T,
156+
std::enable_if_t<!std::is_convertible_v<U&&, T>, bool> = false>
157+
explicit constexpr expected(
158+
U&& value,
159+
std::enable_if_t<std::is_constructible_v<T, U&&> and
160+
!std::is_same_v<std::decay_t<U>, std::in_place_t> and
161+
!std::is_same_v<expected<T, E>, std::decay_t<U>> and
162+
!std::is_same_v<unexpected<E>, std::decay_t<U>>>* = 0)
163+
: has_value_(true) {
164+
new (&union_storage_) U(std::forward<U>(std::move(value)));
165+
}
166+
167+
template <class U = T,
168+
std::enable_if_t<std::is_convertible_v<U&&, T>, bool> = false>
169+
constexpr expected(
170+
U&& value,
171+
std::enable_if_t<std::is_constructible_v<T, U&&> and
172+
!std::is_same_v<std::decay_t<U>, std::in_place_t> and
173+
!std::is_same_v<expected<T, E>, std::decay_t<U>> and
174+
!std::is_same_v<unexpected<E>, std::decay_t<U>>>* = 0)
175+
: has_value_(true) {
176+
new (&union_storage_) U(std::forward<U>(value));
177+
}
178+
179+
template <class G = E,
180+
std::enable_if_t<!std::is_convertible_v<const G&, E>, bool> = false>
181+
explicit constexpr expected(unexpected<G> const& e) : has_value_(false) {
182+
new (&union_storage_) unexpected<E>(e);
183+
}
184+
185+
template <class G = E,
186+
std::enable_if_t<std::is_convertible_v<const G&, E>, bool> = false>
187+
constexpr expected(unexpected<G> const& e) : has_value_(false) {
188+
new (&union_storage_) unexpected<E>(e);
189+
}
190+
191+
template <class G = E,
192+
std::enable_if_t<!std::is_convertible_v<G&&, E>, bool> = false>
193+
explicit constexpr expected(unexpected<G>&& e) noexcept(
194+
std::is_nothrow_move_constructible_v<E, G&&>)
195+
: has_value_(false) {
196+
new (&union_storage_) unexpected<E>(std::move(e.value()));
197+
}
198+
199+
template <class G = E,
200+
std::enable_if_t<std::is_convertible_v<G&&, E>, bool> = false>
201+
constexpr expected(unexpected<G>&& e) noexcept(
202+
std::is_nothrow_constructible_v<E, G&&>)
203+
: has_value_(false) {
204+
new (&union_storage_) unexpected<E>(std::move(e.value()));
205+
}
206+
207+
constexpr explicit operator bool() const noexcept { return has_value_; }
208+
209+
private:
210+
std::aligned_union_t<8, value_type, unexpected_type> union_storage_;
211+
bool has_value_;
212+
};
213+
214+
} // namespace cppnetlib
215+
216+
#endif // CPP_NETLIB_EXPECTED_H_

Diff for: netlib/expected_test.cc

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
#include "netlib/expected.h"
15+
#include "gmock/gmock.h"
16+
#include "gtest/gtest.h"
17+
18+
namespace cppnetlib {
19+
namespace {
20+
21+
using error_code = int;
22+
23+
enum errors : error_code { undefined };
24+
25+
expected<bool, error_code> f(bool b) {
26+
if (b) return true;
27+
return unexpected(errors::undefined);
28+
}
29+
30+
TEST(ExpectedTest, Construction) { expected<bool, error_code> e; }
31+
32+
TEST(ExpectedTest, Unexpected) {
33+
auto e = f(true);
34+
auto g = f(false);
35+
ASSERT_FALSE(g);
36+
ASSERT_TRUE(e);
37+
}
38+
39+
} // namespace
40+
} // namespace cppnetlib

0 commit comments

Comments
 (0)