Skip to content

Commit

Permalink
Add {MakerType,MakerTypeFor,Initializer}::ConstReference().
Browse files Browse the repository at this point in the history
It can avoid moving the object in more cases than `Reference()`, namely when the
constructor argument was a const lvalue reference to a compatible type.

Fix a typo in the variant for `!__cpp_aggregate_bases`.

Polish comments.

PiperOrigin-RevId: 637539030
  • Loading branch information
QrczakMK committed May 27, 2024
1 parent 94fa65c commit b92772a
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 15 deletions.
116 changes: 106 additions & 10 deletions riegeli/base/initializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ class InitializerBase {
// It is preferred to explicitly call `Construct()` instead. This conversion
// allows to pass `Initializer<T>` to another function which accepts a value
// convertible to `T` for construction in-place, including functions like
// `std::vector<T>::emplace_back()` or the constructor of `absl::optional<T>`
// or `absl::StatusOr<T>`.
// `std::make_unique<T>()`, `std::vector<T>::emplace_back()`, or the
// constructor of `absl::optional<T>` or `absl::StatusOr<T>`.
/*implicit*/ operator T() && { return std::move(*this).Construct(); }

private:
Expand Down Expand Up @@ -126,14 +126,32 @@ class InitializerValueBase : public InitializerBase<T> {
using ReferenceStorage = initializer_internal::ReferenceStorage<T>;

// Constructs the `T`, or returns a reference to an already constructed object
// if that was passed to the `Initializer`. This can avoid moving it.
// if that was passed to the `Initializer`.
//
// `Reference()` instead of `Construct()` can avoid moving the object if the
// caller does not need to store the object, or if it will be moved later
// because the target location for the object is not ready yet.
//
// `reference_storage` must outlive usages of the returned reference.
T&& Reference(ReferenceStorage&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND = ReferenceStorage()) && {
return methods()->reference(this->context(), std::move(reference_storage));
}

// Constructs the `T`, or returns a const reference to an already constructed
// object if that was passed to the `Initializer`.
//
// `ConstReference()` can avoid moving the object in more cases than
// `Reference()` if the caller does not need to store the object.
//
// `reference_storage` must outlive usages of the returned reference.
const T& ConstReference(ReferenceStorage&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage()) && {
return methods()->const_reference(this->context(),
std::move(reference_storage));
}

private:
static T&& ReferenceMethodDefault(void* context,
ReferenceStorage&& reference_storage);
Expand All @@ -157,40 +175,65 @@ class InitializerValueBase : public InitializerBase<T> {
static T&& ReferenceMethodFromConstMaker(
void* context, ReferenceStorage&& reference_storage);

static const T& ConstReferenceMethodDefault(
void* context, ReferenceStorage&& reference_storage);

template <typename Arg,
std::enable_if_t<CanBindTo<const T&, Arg&&>::value, int> = 0>
static const T& ConstReferenceMethodFromObject(
void* context, ReferenceStorage&& reference_storage);
template <typename Arg,
std::enable_if_t<!CanBindTo<const T&, Arg&&>::value, int> = 0>
static const T& ConstReferenceMethodFromObject(
void* context, ReferenceStorage&& reference_storage);

template <typename... Args>
static const T& ConstReferenceMethodFromMaker(
void* context, ReferenceStorage&& reference_storage);

template <typename... Args>
static const T& ConstReferenceMethodFromConstMaker(
void* context, ReferenceStorage&& reference_storage);

protected:
struct Methods : InitializerValueBase::InitializerBase::Methods {
T && (*reference)(void* context, ReferenceStorage&& reference_storage);
const T& (*const_reference)(void* context,
ReferenceStorage&& reference_storage);
};

#if __cpp_aggregate_bases
template <typename Dummy = void>
static constexpr Methods kMethodsDefault = {
InitializerValueBase::InitializerBase::template kMethodsDefault<>,
ReferenceMethodDefault};
ReferenceMethodDefault, ConstReferenceMethodDefault};

template <typename Arg>
static constexpr Methods kMethodsFromObject = {
InitializerValueBase::InitializerBase::template kMethodsFromObject<Arg>,
ReferenceMethodFromObject<Arg>};
ReferenceMethodFromObject<Arg>, ConstReferenceMethodFromObject<Arg>};

template <typename... Args>
static constexpr Methods kMethodsFromMaker = {
InitializerValueBase::InitializerBase::template kMethodsFromMaker<
Args...>,
ReferenceMethodFromMaker<Args...>};
ReferenceMethodFromMaker<Args...>,
ConstReferenceMethodFromMaker<Args...>};

template <typename... Args>
static constexpr Methods kMethodsFromConstMaker = {
InitializerValueBase::InitializerBase::template kMethodsFromConstMaker<
Args...>,
ReferenceMethodFromConstMaker<Args...>};
ReferenceMethodFromConstMaker<Args...>,
ConstReferenceMethodFromConstMaker<Args...>};
#else
static constexpr Methods MakeMethodsDefault() {
Methods methods;
static_cast<typename InitializerValueBase::InitializerBase::Methods&>(
methods) =
InitializerValueBase::InitializerBase::template kMethodsDefault<>;
methods.reference = ReferenceMethodDefault;
methods.const_reference = ConstReferenceMethodDefault;
return methods;
}
template <typename Dummy = void>
Expand All @@ -203,6 +246,7 @@ class InitializerValueBase : public InitializerBase<T> {
methods) =
InitializerValueBase::InitializerBase::template kMethodsFromObject<Arg>;
methods.reference = ReferenceMethodFromObject<Arg>;
methods.const_reference = ConstReferenceMethodFromObject<Arg>;
return methods;
}
template <typename Arg>
Expand All @@ -216,6 +260,7 @@ class InitializerValueBase : public InitializerBase<T> {
InitializerValueBase::InitializerBase::template kMethodsFromMaker<
Args...>;
methods.reference = ReferenceMethodFromMaker<Args...>;
methods.const_reference = ConstReferenceMethodFromMaker<Args...>;
return methods;
}
template <typename... Args>
Expand All @@ -229,11 +274,12 @@ class InitializerValueBase : public InitializerBase<T> {
InitializerValueBase::InitializerBase::template kMethodsFromConstMaker<
Args...>;
methods.reference = ReferenceMethodFromConstMaker<Args...>;
methods.const_reference = ConstReferenceMethodFromConstMaker<Args...>;
return methods;
}
template <typename... Args>
static constexpr Methods kMethodsFromConstMaker =
MakeMethodsfromConstMaker<Args...>();
MakeMethodsFromConstMaker<Args...>();
#endif

explicit InitializerValueBase(const Methods* methods)
Expand Down Expand Up @@ -649,15 +695,23 @@ class Initializer<T, allow_explicit,
Initializer(Initializer&& other) = default;
Initializer& operator=(Initializer&&) = delete;

// `Reference()` can be defined in terms of `Construct()` because reference
// storage is never used for reference types.
// `Reference()` and `ConstReference()` can be defined in terms of
// `Construct()` because reference storage is never used for reference types.
//
// Unused `reference_storage` parameter makes the signature compatible with
// the non-reference specialization.
T&& Reference(ABSL_ATTRIBUTE_UNUSED ReferenceStorage reference_storage =
ReferenceStorage()) && {
// `T` is a reference type here, so `T&&` is the same as `T`.
return std::move(*this).Construct();
}
const T& ConstReference(ABSL_ATTRIBUTE_UNUSED ReferenceStorage
reference_storage = ReferenceStorage()) && {
// `T` is a reference type here, but it can be an rvalue reference, in which
// case `const T&` collapses to an lvalue reference.
T reference = std::move(*this).Construct();
return static_cast<const T&>(reference);
}
};

// `InitializerTarget<T>::type` and `InitializerTargetT<T>` deduce the
Expand Down Expand Up @@ -788,6 +842,48 @@ T&& InitializerValueBase<T>::ReferenceMethodFromConstMaker(
std::move(reference_storage));
}

template <typename T>
const T& InitializerValueBase<T>::ConstReferenceMethodDefault(
ABSL_ATTRIBUTE_UNUSED void* context, ReferenceStorage&& reference_storage) {
reference_storage.emplace();
return *reference_storage;
}

template <typename T>
template <typename Arg,
std::enable_if_t<CanBindTo<const T&, Arg&&>::value, int>>
const T& InitializerValueBase<T>::ConstReferenceMethodFromObject(
void* context, ABSL_ATTRIBUTE_UNUSED ReferenceStorage&& reference_storage) {
return std::forward<Arg>(
*static_cast<std::remove_reference_t<Arg>*>(context));
}

template <typename T>
template <typename Arg,
std::enable_if_t<!CanBindTo<const T&, Arg&&>::value, int>>
const T& InitializerValueBase<T>::ConstReferenceMethodFromObject(
void* context, ReferenceStorage&& reference_storage) {
reference_storage.emplace(
std::forward<Arg>(*static_cast<std::remove_reference_t<Arg>*>(context)));
return *reference_storage;
}

template <typename T>
template <typename... Args>
const T& InitializerValueBase<T>::ConstReferenceMethodFromMaker(
void* context, ReferenceStorage&& reference_storage) {
return std::move(*static_cast<MakerType<Args...>*>(context))
.template ConstReference<T>(std::move(reference_storage));
}

template <typename T>
template <typename... Args>
const T& InitializerValueBase<T>::ConstReferenceMethodFromConstMaker(
void* context, ReferenceStorage&& reference_storage) {
return static_cast<const MakerType<Args...>*>(context)
->template ConstReference<T>(std::move(reference_storage));
}

template <typename T>
void InitializerAssignableValueBase<T>::AssignToMethodDefault(
ABSL_ATTRIBUTE_UNUSED void* context, T& object) {
Expand Down
12 changes: 11 additions & 1 deletion riegeli/base/initializer_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ namespace initializer_internal {

// Internal storage which is conditionally needed for storing the object that
// `MakerType<Args...>::Reference<T>()`,
// `MakerTypeFor<T, Args...>::Reference()`, and `Initializer<T>::Reference()`
// `MakerType<Args...>::ConstReference<T>()`,
// `MakerTypeFor<T, Args...>::Reference()`,
// `MakerTypeFor<T, Args...>::ConstReference()`,
// `Initializer<T>::Reference()`, and `Initializer<T>::ConstReference()`
// refer to.
//
// The public name of this type is `MakerType<Args...>::ReferenceStorage<T>`,
Expand Down Expand Up @@ -68,6 +71,12 @@ class ReferenceStorage {
"not initialized";
return std::move(value_);
}
const T& operator*() const& {
RIEGELI_ASSERT(initialized_)
<< "Failed precondition of ReferenceStorage::operator*: "
"not initialized";
return value_;
}

private:
union {
Expand All @@ -93,6 +102,7 @@ class ReferenceStorage<
}

T&& operator*() && { return std::move(value_); }
const T& operator*() const& { return value_; }

private:
union {
Expand Down
107 changes: 103 additions & 4 deletions riegeli/base/maker.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ class MakerType : public ConditionallyAssignable<absl::conjunction<
}

// Constructs the `T`, or returns a reference to an already constructed object
// if that was passed to the `MakerType`. This can avoid moving it.
// if that was passed to the `MakerType`.
//
// `Reference()` instead of `Construct()` can avoid moving the object if the
// caller does not need to store the object, or if it will be moved later
// because the target location for the object is not ready yet.
//
// `reference_storage` must outlive usages of the returned reference.
template <
Expand All @@ -97,6 +101,31 @@ class MakerType : public ConditionallyAssignable<absl::conjunction<
return this->template ReferenceImpl<T>(std::move(reference_storage));
}

// Constructs the `T`, or returns a const reference to an already constructed
// object if that was passed to the `MakerType`.
//
// `ConstReference()` can avoid moving the object in more cases than
// `Reference()` if the caller does not need to store the object.
//
// `reference_storage` must outlive usages of the returned reference.
template <
typename T,
std::enable_if_t<std::is_constructible<T, Args&&...>::value, int> = 0>
const T& ConstReference(ReferenceStorage<T>&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage<T>()) && {
return std::move(*this).template ConstReferenceImpl<T>(
std::move(reference_storage));
}
template <typename T,
std::enable_if_t<std::is_constructible<T, const Args&...>::value,
int> = 0>
const T& ConstReference(ReferenceStorage<T>&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage<T>()) const& {
return this->template ConstReferenceImpl<T>(std::move(reference_storage));
}

// Makes `object` equivalent to the constructed `T`. This avoids constructing
// a temporary `T` and moving from it.
template <typename T,
Expand Down Expand Up @@ -168,6 +197,49 @@ class MakerType : public ConditionallyAssignable<absl::conjunction<
return *std::move(reference_storage);
}

template <
typename T,
std::enable_if_t<
initializer_internal::CanBindTo<const T&, Args&&...>::value, int> = 0>
const T& ConstReferenceImpl(
ABSL_ATTRIBUTE_UNUSED ReferenceStorage<T>&& reference_storage =
ReferenceStorage<T>()) && {
return std::get<0>(std::move(args_));
}
template <typename T, std::enable_if_t<!initializer_internal::CanBindTo<
const T&, Args&&...>::value,
int> = 0>
const T& ConstReferenceImpl(ReferenceStorage<T>&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage<T>()) && {
absl::apply(
[&](Args&&... args) {
reference_storage.emplace(std::forward<Args>(args)...);
},
std::move(args_));
return *reference_storage;
}

template <typename T, std::enable_if_t<initializer_internal::CanBindTo<
const T&, const Args&...>::value,
int> = 0>
const T& ConstReferenceImpl(
ABSL_ATTRIBUTE_UNUSED ReferenceStorage<T>&& reference_storage =
ReferenceStorage<T>()) const& {
return std::get<0>(args_);
}
template <typename T, std::enable_if_t<!initializer_internal::CanBindTo<
const T&, const Args&...>::value,
int> = 0>
const T& ConstReferenceImpl(ReferenceStorage<T>&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage<T>()) const& {
absl::apply(
[&](const Args&... args) { reference_storage.emplace(args...); },
args_);
return *reference_storage;
}

std::tuple<Args...> args_;
};

Expand Down Expand Up @@ -226,13 +298,17 @@ class MakerTypeFor : public ConditionallyAssignable<absl::conjunction<
// It is preferred to explicitly call `Construct()` instead. This conversion
// allows to pass `MakerTypeFor<T, Args...>` to another function which accepts
// a value convertible to `T` for construction in-place, including functions
// like `std::vector<T>::emplace_back()` or the constructor of
// `absl::optional<T>` or `absl::StatusOr<T>`.
// like `std::make_unique<T>()`, `std::vector<T>::emplace_back()`, or the
// constructor of `absl::optional<T>` or `absl::StatusOr<T>`.
/*implicit*/ operator T() && { return std::move(*this).Construct(); }
/*implicit*/ operator T() const& { return Construct(); }

// Constructs the `T`, or returns a reference to an already constructed object
// if that was passed to the `MakerTypeFor`. This can avoid moving it.
// if that was passed to the `MakerTypeFor`.
//
// `Reference()` instead of `Construct()` can avoid moving the object if the
// caller does not need to store the object, or if it will be moved later
// because the target location for the object is not ready yet.
//
// `reference_storage` must outlive usages of the returned reference.
T&& Reference(ReferenceStorage&& reference_storage
Expand All @@ -249,6 +325,29 @@ class MakerTypeFor : public ConditionallyAssignable<absl::conjunction<
return maker_.template Reference<T>(std::move(reference_storage));
}

// Constructs the `T`, or returns a const reference to an already constructed
// object if that was passed to the `MakerTypeFor`.
//
// `ConstReference()` can avoid moving the object in more cases than
// `Reference()` if the caller does not need to store the object.
//
// `reference_storage` must outlive usages of the returned reference.
const T& ConstReference(ReferenceStorage&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage()) && {
return std::move(maker_).template ConstReference<T>(
std::move(reference_storage));
}
template <
typename DependentT = T,
std::enable_if_t<std::is_constructible<DependentT, const Args&...>::value,
int> = 0>
const T& ConstReference(ReferenceStorage&& reference_storage
ABSL_ATTRIBUTE_LIFETIME_BOUND =
ReferenceStorage()) const& {
return maker_.template ConstReference<T>(std::move(reference_storage));
}

// Makes `object` equivalent to the constructed `T`. This avoids constructing
// a temporary `T` and moving from it.
template <typename DependentT = T,
Expand Down

0 comments on commit b92772a

Please sign in to comment.