/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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
*
* http://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.
*/
#pragma once
#include <initializer_list>
#include <new>
#include <stdexcept>
#include <type_traits>
#include <folly/lang/SafeAssert.h>
#include <folly/synchronization/CallOnce.h>
namespace folly {
/**
* DelayedInit -- thread-safe delayed initialization of a value. There are two
* important differences between Lazy and DelayedInit:
* 1. DelayedInit does not store the factory function inline.
* 2. DelayedInit is thread-safe.
*
* Due to these differences, DelayedInit is suitable for data members. Lazy is
* best for local stack variables.
*
* Example Usage:
*
* struct Foo {
* Bar& bar() {
* LargeState state;
* return bar_.try_emplace_with(
* [this, &state] { return computeBar(state); });
* }
* private:
* Bar computeBar(LargeState&);
* DelayedInit<Bar> bar_;
* };
*
* If the above example were to use Lazy instead of DelayedInit:
* - Storage for LargeState and this-pointer would need to be reserved in the
* struct which wastes memory.
* - It would require additional synchronization logic for thread-safety.
*
*
* Rationale:
*
* - The stored value is initialized at most once and never deinitialized.
* Unlike Lazy, the initialization logic must be provided by the consumer.
* This means that DelayedInit is more of a "storage" type like
* std::optional. These semantics are perfect for thread-safe, lazy
* initialization of a data member.
*
* - DelayedInit models neither MoveConstructible nor CopyConstructible. The
* rationale is the same as that of std::once_flag.
*
* - There is no need for a non-thread-safe version of DelayedInit.
* std::optional will suffice in these cases.
*/
template <typename T>
struct DelayedInit {
DelayedInit() = default;
DelayedInit(const DelayedInit&) = delete;
DelayedInit& operator=(const DelayedInit&) = delete;
/**
* Gets the pre-existing value if already initialized or creates the value
* returned by the provided factory function. If the value already exists,
* then the provided function is not called.
*/
template <typename Func>
T& try_emplace_with(Func func) {
auto addr = static_cast<void*>(std::addressof(storage_.value));
call_once(storage_.init, [&] { ::new (addr) T(func()); });
return storage_.value;
}
/**
* Gets the pre-existing value if already initialized or constructs the value
* in-place by direct-initializing with the provided arguments.
*/
template <typename... A>
T& try_emplace(A&&... a) {
return try_emplace_with([&] { return T(static_cast<A&&>(a)...); });
}
template <
typename U,
typename... A,
typename = std::enable_if_t<
std::is_constructible<T, std::initializer_list<U>, A...>::value>>
T& try_emplace(std::initializer_list<U> ilist, A&&... a) {
return try_emplace_with([&] { return T(ilist, static_cast<A&&>(a)...); });
}
bool has_value() const { return test_once(storage_.init); }
explicit operator bool() const { return has_value(); }
T& value() {
require_value();
return storage_.value;
}
const T& value() const {
require_value();
return storage_.value;
}
T& operator*() {
FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
return storage_.value;
}
const T& operator*() const {
FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
return storage_.value;
}
T* operator->() {
FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
return std::addressof(storage_.value);
}
const T* operator->() const {
FOLLY_SAFE_DCHECK(has_value(), "tried to access empty DelayedInit");
return std::addressof(storage_.value);
}
private:
void require_value() const {
if (!has_value()) {
throw_exception<std::logic_error>("tried to access empty DelayedInit");
}
}
using OnceFlag = std::conditional_t<
alignof(T) >= sizeof(once_flag),
once_flag,
compact_once_flag>;
struct StorageTriviallyDestructible {
union {
std::remove_const_t<T> value;
};
OnceFlag init;
StorageTriviallyDestructible() {}
};
struct StorageNonTriviallyDestructible {
union {
std::remove_const_t<T> value;
};
OnceFlag init;
StorageNonTriviallyDestructible() {}
~StorageNonTriviallyDestructible() {
if (test_once(this->init)) {
this->value.~T();
}
}
};
using Storage = std::conditional_t<
std::is_trivially_destructible<T>::value,
StorageTriviallyDestructible,
StorageNonTriviallyDestructible>;
Storage storage_;
};
} // namespace folly