dlib::optional (#2815)

* backport of std::optional for c++11. WIP

* wip

* whoops

* wip

* wip

* holy shit this is tiring

* type traits

* more traits

* more traits

* wip

* delimiters

* bad header include

* noexcept correctness

* fixed has_value()
more tests

* started adding monadic intefaces

* compile time contract

* added transform

* added or_else

* according to the standard, swap also empties objects when necessary

* noexcept tests

* beginning monad tests

* bug fix

* contexpr monads

* added docs

* more tests and bug fixes

* - bug fix
- test inplace constructors and emplace()

* test value_or()

* more tests

---------

Co-authored-by: pf <pf@me>
Co-authored-by: Your Name <you@example.com>
This commit is contained in:
pfeatherstone 2023-07-08 14:12:56 +01:00 committed by GitHub
parent 4d2db66cc5
commit 4e22715eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 1493 additions and 1 deletions

1017
dlib/optional.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -161,6 +161,7 @@ add_executable(${target_name} main.cpp tester.cpp
elastic_net.cpp
te.cpp
ffmpeg.cpp
optional.cpp
)
get_filename_component(DLIB_FFMPEG_DATA ${CMAKE_SOURCE_DIR}/ffmpeg_data/details.cfg REALPATH)

465
dlib/test/optional.cpp Normal file
View File

@ -0,0 +1,465 @@
// Copyright (C) 2023 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#include <dlib/optional.h>
#include "tester.h"
namespace
{
using namespace test;
using namespace dlib;
logger dlog("test.optional");
// ---------------------------------------------------------------------------------------------------
static_assert(std::is_copy_constructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_copy_assignable<dlib::optional<int>>::value, "bad");
static_assert(std::is_move_constructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_move_assignable<dlib::optional<int>>::value, "bad");
static_assert(std::is_destructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_trivially_copy_constructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_trivially_copy_assignable<dlib::optional<int>>::value, "bad");
static_assert(std::is_trivially_move_constructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_trivially_move_assignable<dlib::optional<int>>::value, "bad");
static_assert(std::is_trivially_destructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_nothrow_copy_constructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_nothrow_copy_assignable<dlib::optional<int>>::value, "bad");
static_assert(std::is_nothrow_move_constructible<dlib::optional<int>>::value, "bad");
static_assert(std::is_nothrow_move_assignable<dlib::optional<int>>::value, "bad");
static_assert(std::is_nothrow_destructible<dlib::optional<int>>::value, "bad");
struct trivial_type
{
trivial_type(const trivial_type&) = default;
trivial_type(trivial_type&&) = default;
trivial_type& operator=(const trivial_type&) = default;
trivial_type& operator=(trivial_type&&) = default;
~trivial_type() = default;
};
static_assert(std::is_copy_constructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_copy_assignable<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_move_constructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_move_assignable<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_destructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_trivially_copy_constructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_trivially_copy_assignable<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_trivially_move_constructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_trivially_move_assignable<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_trivially_destructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_nothrow_copy_constructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_nothrow_copy_assignable<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_nothrow_move_constructible<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_nothrow_move_assignable<dlib::optional<trivial_type>>::value, "bad");
static_assert(std::is_nothrow_destructible<dlib::optional<trivial_type>>::value, "bad");
struct non_trivial_type
{
non_trivial_type(const non_trivial_type&) {}
non_trivial_type(non_trivial_type&&) {};
non_trivial_type& operator=(const non_trivial_type&) { return *this; }
non_trivial_type& operator=(non_trivial_type&&) { return *this; };
~non_trivial_type() {}
};
static_assert(std::is_copy_constructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(std::is_copy_assignable<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(std::is_move_constructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(std::is_move_assignable<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(std::is_destructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_trivially_copy_constructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_trivially_copy_assignable<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_trivially_move_constructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_trivially_move_assignable<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_trivially_destructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_nothrow_copy_constructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_nothrow_copy_assignable<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_nothrow_move_constructible<dlib::optional<non_trivial_type>>::value, "bad");
static_assert(!std::is_nothrow_move_assignable<dlib::optional<non_trivial_type>>::value, "bad");
struct nothing_works
{
nothing_works(const nothing_works&) = delete;
nothing_works(nothing_works&&) = delete;
nothing_works& operator=(const nothing_works&) = delete;
nothing_works& operator=(nothing_works&&) = delete;
};
static_assert(!std::is_copy_constructible<dlib::optional<nothing_works>>::value, "bad");
static_assert(!std::is_copy_assignable<dlib::optional<nothing_works>>::value, "bad");
static_assert(!std::is_move_constructible<dlib::optional<nothing_works>>::value, "bad");
static_assert(!std::is_move_assignable<dlib::optional<nothing_works>>::value, "bad");
struct copyable_type
{
copyable_type(const copyable_type&) = default;
copyable_type(copyable_type&&) = delete;
copyable_type& operator=(const copyable_type&) = default;
copyable_type& operator=(copyable_type&&) = delete;
};
static_assert(std::is_copy_constructible<dlib::optional<copyable_type>>::value, "bad");
static_assert(std::is_copy_assignable<dlib::optional<copyable_type>>::value, "bad");
//copyable_type can still be moved, but it will just copy.
struct moveable_type
{
moveable_type(const moveable_type&) = delete;
moveable_type(moveable_type&&) = default;
moveable_type& operator=(const moveable_type&) = delete;
moveable_type& operator=(moveable_type&&) = default;
};
static_assert(!std::is_copy_constructible<dlib::optional<moveable_type>>::value, "bad");
static_assert(!std::is_copy_assignable<dlib::optional<moveable_type>>::value, "bad");
static_assert(std::is_move_constructible<dlib::optional<moveable_type>>::value, "bad");
static_assert(std::is_move_assignable<dlib::optional<moveable_type>>::value, "bad");
// ---------------------------------------------------------------------------------------------------
void test_optional_int()
{
dlib::optional<int> o1;
DLIB_TEST(!o1);
DLIB_TEST(!o1.has_value());
DLIB_TEST(o1.value_or(2) == 2);
int throw_counter{0};
try {
o1.value();
} catch(const std::exception& e) {
throw_counter++;
}
DLIB_TEST(throw_counter == 1);
DLIB_TEST(o1 == dlib::nullopt);
dlib::optional<int> o2 = dlib::nullopt;
static_assert(noexcept(o2 = dlib::nullopt), "bad");
DLIB_TEST(!o2);
DLIB_TEST(!o2.has_value());
DLIB_TEST(o2.value_or(3) == 3);
dlib::optional<int> o3 = 42;
DLIB_TEST(*o3 == 42);
DLIB_TEST(o3.value() == 42);
DLIB_TEST(o3.value_or(3) == 42);
DLIB_TEST(o3 == 42);
DLIB_TEST(42 == o3);
dlib::optional<int> o4 = o3;
static_assert(noexcept(o4 = o3), "bad");
DLIB_TEST(*o3 == 42);
DLIB_TEST(*o4 == 42);
DLIB_TEST(o4 == 42);
DLIB_TEST(42 == o4);
DLIB_TEST(o4.value() == 42);
dlib::optional<int> o5 = o1;
static_assert(noexcept(o5 = o1), "bad");
DLIB_TEST(!o1);
DLIB_TEST(!o5);
DLIB_TEST(!o5.has_value());
dlib::optional<int> o6 = std::move(o3);
static_assert(noexcept(o6 = std::move(o3)), "bad");
DLIB_TEST(*o6 == 42);
DLIB_TEST(o6.value() == 42);
dlib::optional<short> o7 = (short)42;
static_assert(noexcept(o7 = (short)42), "bad");
DLIB_TEST(*o7 == 42);
dlib::optional<int> o8 = o7;
DLIB_TEST(*o7 == 42);
DLIB_TEST(*o8 == 42);
dlib::optional<int> o9 = std::move(o7);
DLIB_TEST(*o9 == 42);
dlib::optional<int> o10;
DLIB_TEST(!o10);
o10 = o3;
DLIB_TEST(*o10 == 42);
o10 = dlib::nullopt;
DLIB_TEST(!o10);
dlib::optional<int> o11;
o11 = std::move(o3);
DLIB_TEST(*o11 == 42);
o11 = (short)12;
DLIB_TEST(*o11 == 12);
dlib::optional<int> o12;
swap(o12, o4);
DLIB_TEST(o12);
DLIB_TEST(!o4);
DLIB_TEST(*o12 == 42);
static_assert(noexcept(swap(o12, o4)), "bad");
o4.reset();
DLIB_TEST(!o4);
}
// ---------------------------------------------------------------------------------------------------
void test_optional_int_constexpr()
{
{
constexpr dlib::optional<int> o2{};
constexpr dlib::optional<int> o3 = {};
constexpr dlib::optional<int> o4 = dlib::nullopt;
constexpr dlib::optional<int> o5 = {dlib::nullopt};
constexpr dlib::optional<int> o6(dlib::nullopt);
static_assert(!o2, "bad");
static_assert(!o3, "bad");
static_assert(!o4, "bad");
static_assert(!o5, "bad");
static_assert(!o6, "bad");
static_assert(o2.value_or(1) == 1, "bad");
static_assert(o3.value_or(1) == 1, "bad");
static_assert(o4.value_or(1) == 1, "bad");
static_assert(o5.value_or(1) == 1, "bad");
static_assert(o6.value_or(1) == 1, "bad");
static_assert(o2 != 1, "bad");
static_assert(1 != o2, "bad");
}
}
// ---------------------------------------------------------------------------------------------------
void test_optional_int_monads()
{
dlib::optional<int> o1{42};
{
auto res = o1.and_then([](int i) { return dlib::optional<long>(i); });
static_assert(std::is_same<decltype(res), dlib::optional<long>>::value, "bad map");
DLIB_TEST(*res == 42);
}
{
auto res = o1.transform([](int i) { return (long)i; });
static_assert(std::is_same<decltype(res), dlib::optional<long>>::value, "bad map");
DLIB_TEST(*res == 42);
}
{
auto res = o1.and_then([](int i) { return dlib::optional<std::string>(std::to_string(i)); });
static_assert(std::is_same<decltype(res), dlib::optional<std::string>>::value, "bad map");
DLIB_TEST(*res == "42");
}
{
auto res = o1.transform([](int i) { return std::to_string(i); });
static_assert(std::is_same<decltype(res), dlib::optional<std::string>>::value, "bad map");
DLIB_TEST(*res == "42");
}
{
auto res = o1.transform([](int i) {return i+1;})
.transform([](int i) {return i*2;})
.transform([](int i) {return std::to_string(i);})
.transform([](const std::string& str) {return std::stoi(str);})
.transform([](int i) {return i - 2;})
.or_else([] {return dlib::make_optional<int>(0);});
static_assert(std::is_same<decltype(res), dlib::optional<int>>::value, "bad map");
DLIB_TEST(*res == 84);
}
dlib::optional<int> o2;
{
auto res = o2.transform([](int i) {return i+1;})
.or_else([] {return dlib::make_optional<int>(0);});
static_assert(std::is_same<decltype(res), dlib::optional<int>>::value, "bad map");
DLIB_TEST(*res == 0);
}
}
// ---------------------------------------------------------------------------------------------------
void test_optional_int_constexpr_monads()
{
constexpr dlib::optional<int> o1{42};
{
struct callback
{
constexpr auto operator()(int i) {return dlib::optional<long>{i}; };
};
constexpr auto res = o1.and_then(callback{});
static_assert(std::is_same<std::decay_t<decltype(res)>, dlib::optional<long>>::value, "bad map");
static_assert(*res == 42, "bad");
DLIB_TEST(*res == 42);
}
}
// ---------------------------------------------------------------------------------------------------
static int constructor_count{0};
static int copy_constructor_count{0};
static int move_constructor_count{0};
static int copy_assign_count{0};
static int move_assign_count{0};
static void reset_counters()
{
constructor_count = 0;
copy_constructor_count = 0;
move_constructor_count = 0;
copy_assign_count = 0;
move_assign_count = 0;
}
struct optional_dummy
{
optional_dummy() {++constructor_count;}
optional_dummy(const optional_dummy&) {++copy_constructor_count;}
optional_dummy(optional_dummy&&) {++move_constructor_count;}
optional_dummy& operator=(const optional_dummy&) {++copy_assign_count; return *this;}
optional_dummy& operator=(optional_dummy&&) {++move_assign_count; return *this;}
};
void test_constructors()
{
dlib::optional<optional_dummy> val;
DLIB_TEST(constructor_count == 0);
DLIB_TEST(copy_constructor_count == 0);
DLIB_TEST(move_constructor_count == 0);
DLIB_TEST(copy_assign_count == 0);
DLIB_TEST(move_assign_count == 0);
val = optional_dummy{};
DLIB_TEST(constructor_count == 1);
DLIB_TEST(copy_constructor_count == 0);
DLIB_TEST(move_constructor_count == 1);
DLIB_TEST(copy_assign_count == 0);
DLIB_TEST(move_assign_count == 0);
reset_counters();
dlib::optional<optional_dummy> val2{val};
DLIB_TEST(constructor_count == 0);
DLIB_TEST(copy_constructor_count == 1);
DLIB_TEST(move_constructor_count == 0);
DLIB_TEST(copy_assign_count == 0);
DLIB_TEST(move_assign_count == 0);
reset_counters();
dlib::optional<optional_dummy> val3{std::move(val)};
DLIB_TEST(constructor_count == 0);
DLIB_TEST(copy_constructor_count == 0);
DLIB_TEST(move_constructor_count == 1);
DLIB_TEST(copy_assign_count == 0);
DLIB_TEST(move_assign_count == 0);
reset_counters();
val2 = val;
DLIB_TEST(constructor_count == 0);
DLIB_TEST(copy_constructor_count == 0);
DLIB_TEST(move_constructor_count == 0);
DLIB_TEST(copy_assign_count == 1);
DLIB_TEST(move_assign_count == 0);
reset_counters();
val3 = std::move(val);
DLIB_TEST(constructor_count == 0);
DLIB_TEST(copy_constructor_count == 0);
DLIB_TEST(move_constructor_count == 0);
DLIB_TEST(copy_assign_count == 0);
DLIB_TEST(move_assign_count == 1);
reset_counters();
}
// ---------------------------------------------------------------------------------------------------
void test_emplace()
{
struct A
{
A() = default;
explicit A(int i, float f, const std::string& str) : i_{i}, f_{f}, str_{str} {}
int i_{0};
float f_{0.0f};
std::string str_;
};
dlib::optional<A> o1(dlib::in_place, 1, 2.5f, "hello there");
DLIB_TEST(o1);
DLIB_TEST(o1->i_ == 1);
DLIB_TEST(o1->f_ == 2.5f);
DLIB_TEST(o1->str_ == "hello there");
dlib::optional<A> o2;
o2.emplace(2, 5.1f, "general kenobi");
DLIB_TEST(o2);
DLIB_TEST(o2->i_ == 2);
DLIB_TEST(o2->f_ == 5.1f);
DLIB_TEST(o2->str_ == "general kenobi");
auto o3 = dlib::make_optional<A>(3, 3.141592f, "from a certain point of view");
DLIB_TEST(o3);
DLIB_TEST(o3->i_ == 3);
DLIB_TEST(o3->f_ == 3.141592f);
DLIB_TEST(o3->str_ == "from a certain point of view");
dlib::optional<std::string> o4(dlib::in_place, {'a', 'b', 'c'});
DLIB_TEST(o4);
DLIB_TEST(*o4 == "abc");
DLIB_TEST(o4 == "abc");
dlib::optional<std::string> o5;
o5.emplace({'a', 'b', 'c'});
DLIB_TEST(o5);
DLIB_TEST(*o5 == "abc");
}
// ---------------------------------------------------------------------------------------------------
void test_make_optional()
{
constexpr auto o1 = dlib::make_optional(1);
static_assert(std::is_same<std::decay_t<decltype(o1)>, dlib::optional<int>>::value, "bad");
static_assert(o1 == 1, "bad");
static_assert(1 == o1, "bad");
}
class optional_tester : public tester
{
public:
optional_tester (
) :
tester ("test_optional",
"Runs tests on the optional object")
{}
void perform_test (
)
{
test_optional_int();
test_optional_int_constexpr();
test_optional_int_monads();
test_optional_int_constexpr_monads();
test_constructors();
test_emplace();
test_make_optional();
}
} a;
}

View File

@ -4,7 +4,7 @@
#define DLIB_UTILITY_Hh_
#include <cstddef>
#include <type_traits>
#include <utility>
/*
This header contains back-ports of C++14/17 functions and type traits
@ -67,6 +67,15 @@ namespace dlib
using pop_front_t = typename pop_front<Sequence>::type;
// ---------------------------------------------------------------------
struct in_place_t
{
explicit in_place_t() = default;
};
static constexpr in_place_t in_place{};
// ---------------------------------------------------------------------
}
#endif //DLIB_UTILITY_Hh_