Skip to content

Commit

Permalink
Started work on lazy_init.
Browse files Browse the repository at this point in the history
  • Loading branch information
jrgfogh committed Apr 14, 2024
1 parent cfb7c02 commit a0c5982
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 1 deletion.
44 changes: 44 additions & 0 deletions sw/lazy_init.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

#include <mutex>
#include <utility>
#include <memory>

namespace sw
{
template <typename ValueType, typename InitType = std::move_only_function<typename ValueType()>>
// TODO(jrgfogh): Constraint InitType.
class lazy_init final
{
mutable std::unique_ptr<ValueType> data_;
mutable InitType init_;

void ensure_initialized() const
{
if (!data_)
{
data_ = std::make_unique<ValueType>(init_());
}
}
public:
using value_type = ValueType;
using init_type = InitType;

explicit lazy_init(InitType init) :
init_{std::move(init)}
{
}

auto operator*() -> ValueType&
{
ensure_initialized();
return *data_;
}

auto operator*() const -> ValueType const &
{
ensure_initialized();
return *data_;
}
};
}
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
add_executable(
small_wrappers_tests
propagate_const_tests.cpp)
"propagate_const_tests.cpp"
"lazy_init_tests.cpp")
target_link_libraries(
small_wrappers_tests
GTest::gtest_main
Expand Down
104 changes: 104 additions & 0 deletions tests/lazy_init_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "../sw/lazy_init.h"

#include "gtest_unwarn.h"

#include <type_traits>
#include <utility>
#include <memory>
#include <string>
#include <ranges>

template<typename T>
concept lazy_init_specialization =
std::is_constructible_v<T, typename T::init_type> &&
std::is_same_v<decltype(*std::declval<T>()), typename T::value_type&> &&
std::is_same_v<decltype(*std::declval<T const>()), typename T::value_type const&>
// I'm not sure if this is a bug or a feature, but at least it's documented here:
//!std::is_move_assignable_v<T> &&
//!std::is_move_constructible_v<T> &&
//!std::is_copy_assignable_v<T> &&
//!std::is_copy_constructible_v<T>
;

struct not_default_constructible alignas(16)
{
explicit not_default_constructible() = delete;
explicit not_default_constructible(int) {}
};

static_assert(lazy_init_specialization<sw::lazy_init<double, int(*)()>>);
static_assert(lazy_init_specialization<sw::lazy_init<not_default_constructible, not_default_constructible(*)()>>);
static_assert(lazy_init_specialization<sw::lazy_init<std::string, std::string(*)()>>);
static_assert(lazy_init_specialization<sw::lazy_init<std::shared_ptr<int>, std::nullptr_t(*)()>>);

TEST(LazyInit, IsLazy)
{
bool called = false;
sw::lazy_init<int> li{
[&] {
called = true;
return 5;
}};
EXPECT_FALSE(called);
}

TEST(LazyInit, InitExactlyOnce)
{
int callCount = 0;
sw::lazy_init<int> li{
[&] {
callCount++;
return 5;
}};
*li;
*li;
*li;
EXPECT_EQ(callCount, 1);
}

TEST(LazyInit, RetryOnFailure)
{
bool called = false;
sw::lazy_init<int> li{
[&] {
if (!called)
{
called = true;
throw std::exception{};
}
return 5;
}};
EXPECT_THROW((*li), std::exception);
EXPECT_EQ(*li, 5);
}

TEST(LazyInit, DoesNotRequireDefaultConstructibleData)
{
sw::lazy_init<not_default_constructible> li{
[] {
return not_default_constructible{5};
}};
}

TEST(LazyInit, StarOperator)
{
for (auto i : std::ranges::iota_view(5, 10))
{
sw::lazy_init<int> li{
[&] {
return i;
} };
sw::lazy_init<int> const& const_ref = li;
EXPECT_EQ(*li, i);
EXPECT_EQ(*const_ref, i);
}
}

// TODO:
// Implement operators
// Arrow
// Equality (conditionally)
// Spaceship (conditionally)
// Ensure thread safety.
// Null-check function pointers.
// Copy-only function types

0 comments on commit a0c5982

Please sign in to comment.