diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a290fc..f7daf70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,6 +224,7 @@ set(SOURCE_FILES src/tupleHash.h src/ThreadPool.cpp src/ThreadPool.h src/ObjectPool.h + src/Lazy.h ) add_executable(rhubarb ${SOURCE_FILES}) target_link_libraries(rhubarb ${Boost_LIBRARIES} cppFormat sphinxbase pocketSphinx flite webRTC) @@ -239,6 +240,7 @@ set(TEST_FILES tests/pairsTests.cpp tests/tokenizationTests.cpp tests/g2pTests.cpp + tests/LazyTests.cpp src/stringTools.cpp src/stringTools.h src/Timeline.h src/TimeRange.cpp src/TimeRange.h @@ -249,6 +251,7 @@ set(TEST_FILES src/g2p.cpp src/g2p.h src/logging.cpp src/logging.h src/tools.cpp src/tools.h + src/Lazy.h ) add_executable(runTests ${TEST_FILES}) target_link_libraries(runTests gtest gmock gmock_main flite cppFormat) diff --git a/src/Lazy.h b/src/Lazy.h new file mode 100644 index 0000000..41f25ff --- /dev/null +++ b/src/Lazy.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include + +template +class Lazy { +public: + using value_type = T; + + explicit Lazy(std::function createValue) : + createValue(createValue) + {} + + explicit operator bool() const { + return static_cast(_value); + } + + T& value() { + init(); + return *_value; + } + + const T& value() const { + init(); + return *_value; + } + + T* operator->() { + return &value(); + } + + const T* operator->() const { + return &value(); + } + + T& operator*() { + return value(); + } + + const T& operator*() const { + return value(); + } +private: + void init() const { + std::call_once(initialized, [&] { _value = std::make_unique(createValue()); }); + } + + std::function createValue; + mutable std::once_flag initialized; + mutable std::unique_ptr _value; +}; diff --git a/tests/LazyTests.cpp b/tests/LazyTests.cpp new file mode 100644 index 0000000..5172ac1 --- /dev/null +++ b/tests/LazyTests.cpp @@ -0,0 +1,58 @@ +#include +#include "Lazy.h" + +using namespace testing; +using std::make_unique; + +// Not copyable, no default constrctor, movable +struct Foo { + const int value; + Foo(int value) : value(value) {} + + Foo() = delete; + Foo(const Foo&) = delete; + Foo& operator=(const Foo &) = delete; + + Foo(Foo&&) = default; + Foo& operator=(Foo&&) = default; +}; + +TEST(Lazy, basicUsage) { + bool lambdaCalled = false; + Lazy lazy([&lambdaCalled] { lambdaCalled = true; return Foo(42); }); + EXPECT_FALSE(lambdaCalled); + EXPECT_FALSE(static_cast(lazy)); + EXPECT_EQ(42, (*lazy).value); + EXPECT_EQ(42, lazy.value().value); + EXPECT_EQ(42, lazy->value); + EXPECT_TRUE(lambdaCalled); + EXPECT_TRUE(static_cast(lazy)); +} + +TEST(Lazy, constUsage) { + bool lambdaCalled = false; + const Lazy lazy([&lambdaCalled] { lambdaCalled = true; return Foo(42); }); + EXPECT_FALSE(lambdaCalled); + EXPECT_FALSE(static_cast(lazy)); + EXPECT_EQ(42, (*lazy).value); + EXPECT_EQ(42, lazy.value().value); + EXPECT_EQ(42, lazy->value); + EXPECT_TRUE(lambdaCalled); + EXPECT_TRUE(static_cast(lazy)); +} + +using Expensive = Foo; +#define member value; + +TEST(Lazy, demo) { + // Constructor takes function + Lazy lazy([] { return Expensive(42); }); + + // Multiple ways to access value + Expensive& a = *lazy; + Expensive& b = lazy.value(); + auto c = lazy->member; + + // Check if initialized + if (lazy) { /* ... */ } +} \ No newline at end of file