Skip to content

Add a simple TaggedUnion defined in terms of ExternalUnion #29586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion include/swift/Basic/ExternalUnion.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ struct ExternalUnionMembers {
static constexpr int maybeIndexOf() {
return ExternalUnionImpl::indexOf<T, Members...>::value;
}

template <class T>
static constexpr bool contains() {
return ExternalUnionImpl::indexOf<T, Members...>::value != -1;
}
};

/// An external union that uses the member-list index as the user-facing
Expand Down Expand Up @@ -237,7 +242,7 @@ class BasicExternalUnion {
Members::Info::copyAssignSame(unsigned(thisIndex),
Storage, other.Storage);
} else {
destruct(thisIndex, Storage);
destruct(thisIndex);
copyConstruct(otherIndex, other);
}
}
Expand Down
259 changes: 259 additions & 0 deletions include/swift/Basic/TaggedUnion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
//===- TaggedUnion.h - A tagged union ---------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file provides a simple tagged union, like std::variant but without
// any effort to be exception-safe and therefore much simpler.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_TAGGEDUNION_H
#define SWIFT_BASIC_TAGGEDUNION_H

#include "swift/Basic/ExternalUnion.h"

namespace swift {

namespace TaggedUnionImpl {
template <class T>
using simplify_member_type
= typename std::remove_const<typename std::remove_reference<T>::type>::type;

/// Given a type `T` deduced from a `T &&` parameter, can it be used to
/// construct a member of the union? If so, `simplify_member_type<T>`
/// will give the formal member type.
template <class Members, class T>
inline constexpr bool is_member_constructible() {
return Members::template contains<simplify_member_type<T>>();
}

struct Empty {};
}

template <class KindHelper, class Members,
bool NonTrivial = !Members::Info::is_trivially_copyable,
bool HasVoid = Members::template contains<void>()>
class TaggedUnionBase;

/// The base partial specialization. All the other partial specializations
/// will end up inheriting from this.
template <class KindHelper, class Members>
class TaggedUnionBase<KindHelper, Members,
/*NonTrivial*/ false,
/*HasVoid*/ false> {
protected:
using StorageType = SimpleExternalUnionBase<KindHelper, Members>;
using Kind = typename KindHelper::Kind;
StorageType Storage;
Kind TheKind;

TaggedUnionBase(Kind theKind) : TheKind(theKind) {}

public:
/// Construct the union with a value of the given type, which must
/// (ignoring references) be one of the declared members of the union.
template <class T>
TaggedUnionBase(T &&value,
typename std::enable_if<
TaggedUnionImpl::is_member_constructible<Members, T>(),
TaggedUnionImpl::Empty>::type = {}) {
using TargetType = TaggedUnionImpl::simplify_member_type<T>;
TheKind = StorageType::template kindForMember<TargetType>();
Storage.template emplace<TargetType>(TheKind, std::forward<T>(value));
}

template <class T>
typename std::enable_if<TaggedUnionImpl::is_member_constructible<Members, T>(),
TaggedUnionBase &>::type
operator=(T &&value) {
using TargetType = TaggedUnionImpl::simplify_member_type<T>;
TheKind = StorageType::template kindForMember<TargetType>();
Storage.template emplace<TargetType>(TheKind, std::forward<T>(value));
return *this;
}

/// Replace the current value in the union with a value of the given
/// type, which must be one of the declared members of the union.
///
/// The value will be constructed from the arguments with an argument
/// list (i.e. `new(ptr) T(...)`). If aggregate initialization is required,
/// use `emplaceAggregate`.
template <class T, class... Args>
T &emplace(Args &&... args) {
Storage.destruct(TheKind);
TheKind = StorageType::template kindForMember<T>();
return Storage.template emplace<T>(TheKind, std::forward<Args>(args)...);
}

/// Replace the current value in the union with a value of the given
/// type, which must be one of the declared members of the union.
///
/// The value will be constructed from the arguments with a braced
/// initializer list (i.e. `T{...}`) and therefore may use aggregate
/// initialization.
template <class T, class... Args>
T &emplaceAggregate(Args &&... args) {
Storage.destruct(TheKind);
TheKind = StorageType::template kindForMember<T>();
return Storage.template emplaceAggregate<T>(TheKind,
std::forward<Args>(args)...);
}

/// Return true if the union is storing a value of the given type,
/// which must be one of the declared members of the union.
template <class T>
bool isa() const {
return TheKind == StorageType::template kindForMember<T>();
}

/// Return a pointer to the value if the union is storing a value of the
/// given type, which must be one of the declared members of the union.
template <class T>
T *dyn_cast() {
return Storage.template dyn_cast<T>(TheKind);
}

/// Return a pointer to the value if the union is storing a value of the
/// given type, which must be one of the declared members of the union.
template <class T>
const T *dyn_cast() const {
return Storage.template dyn_cast<T>(TheKind);
}

/// Assert that the union is storing a value of the given type and return
/// a reference to it.
template <class T>
T &get() {
return Storage.template get<T>(TheKind);
}

/// Assert that the union is storing a value of the given type and return
/// a reference to it.
template <class T>
const T &get() const {
return Storage.template get<T>(TheKind);
}
};

/// The partial specialization for when the union isn't trivially-copyable.
template <class KindHelper, class Members>
class TaggedUnionBase<KindHelper, Members, /*NonTrivial*/ true, /*HasVoid*/ false>
: public TaggedUnionBase<KindHelper, Members, false, false> {
using super = TaggedUnionBase<KindHelper, Members, false, false>;

protected:
using super::Storage;
using super::TheKind;

TaggedUnionBase(typename super::Kind kind) : super(kind) {}

public:
template <class T>
TaggedUnionBase(T &&value,
typename std::enable_if<
TaggedUnionImpl::is_member_constructible<Members, T>(),
TaggedUnionImpl::Empty>::type = {})
: super(std::forward<T>(value)) {}

TaggedUnionBase(const TaggedUnionBase &other) : super(other.TheKind) {
Storage.copyConstruct(other.TheKind, other.Storage);
}

TaggedUnionBase(TaggedUnionBase &&other) : super(other.TheKind) {
Storage.moveConstruct(other.TheKind, std::move(other.Storage));
}

TaggedUnionBase &operator=(const TaggedUnionBase &other) {
Storage.copyAssign(TheKind, other.TheKind, other.Storage);
TheKind = other.TheKind;
return *this;
}

TaggedUnionBase &operator=(TaggedUnionBase &&other) {
Storage.moveAssign(TheKind, other.TheKind, std::move(other.Storage));
TheKind = other.TheKind;
return *this;
}

~TaggedUnionBase() {
Storage.destruct(TheKind);
}
};

/// The partial specialization for when `void` is a member of the union.
template <class KindHelper, class Members, bool NonTrivial>
class TaggedUnionBase<KindHelper, Members, NonTrivial, /*HasVoid*/ true>
: public TaggedUnionBase<KindHelper, Members, NonTrivial, false> {
using super = TaggedUnionBase<KindHelper, Members, NonTrivial, false>;

protected:
using super::Storage;
using super::TheKind;

static constexpr typename super::Kind kindForVoid() {
return super::StorageType::template kindForMember<void>();
}

public:
template <class T>
TaggedUnionBase(T &&value,
typename std::enable_if<
TaggedUnionImpl::is_member_constructible<Members, T>(),
TaggedUnionImpl::Empty>::type = {})
: super(std::forward<T>(value)) {}

/// Construct the union in the empty state.
TaggedUnionBase() : super(kindForVoid()) {}

/// Test whether the union is in the empty state.
bool empty() const {
return TheKind == kindForVoid();
}

/// Reset the union to the empty state.
void reset() {
Storage.destruct(TheKind);
TheKind = kindForVoid();
}
};

/// A tagged union of the given types.
///
/// Non-trivial members are supported; they will make the union
/// non-trivial to copy, move, and destruct.
///
/// The union provides the following members, as described in the
/// documentation for the primary partial specialization of
/// TaggedUnionBase. In the following, `M` must be a declared member
/// type of the union.
///
/// ```
/// TaggedUnion(M);
/// M &emplace<M>(T...);
/// M &emplaceAggregate<M>(T...);
/// bool isa<M>() const;
/// M *dyn_cast<M>();
/// const M *dyn_cast<M>() const;
/// M &get<M>();
/// const M &get<M>() const;
/// ```
///
/// Additionally, if `void` is one of the types, the union supports an
/// empty state and provides a default constructor as well as `empty()`
/// and `reset()` methods.
template <class... Members>
using TaggedUnion =
TaggedUnionBase<ExternalUnionImpl::OptimalKindTypeHelper<sizeof...(Members)>,
ExternalUnionMembers<Members...>>;

}

#endif
1 change: 1 addition & 0 deletions unittests/Basic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_swift_unittest(SwiftBasicTests
STLExtrasTest.cpp
StringExtrasTest.cpp
SuccessorMapTest.cpp
TaggedUnionTest.cpp
ThreadSafeRefCntPointerTest.cpp
TransformRangeTest.cpp
TreeScopedHashTableTest.cpp
Expand Down
Loading