Skip to content

Commit 319cc79

Browse files
committed
Add a simple TaggedUnion defined in terms of ExternalUnion. NFC.
1 parent 713e636 commit 319cc79

File tree

4 files changed

+485
-1
lines changed

4 files changed

+485
-1
lines changed

include/swift/Basic/ExternalUnion.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ struct ExternalUnionMembers {
8787
static constexpr int maybeIndexOf() {
8888
return ExternalUnionImpl::indexOf<T, Members...>::value;
8989
}
90+
91+
template <class T>
92+
static constexpr bool contains() {
93+
return ExternalUnionImpl::indexOf<T, Members...>::value != -1;
94+
}
9095
};
9196

9297
/// An external union that uses the member-list index as the user-facing
@@ -237,7 +242,7 @@ class BasicExternalUnion {
237242
Members::Info::copyAssignSame(unsigned(thisIndex),
238243
Storage, other.Storage);
239244
} else {
240-
destruct(thisIndex, Storage);
245+
destruct(thisIndex);
241246
copyConstruct(otherIndex, other);
242247
}
243248
}

include/swift/Basic/TaggedUnion.h

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
//===- TaggedUnion.h - A tagged union ---------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file provides a simple tagged union, like std::variant but without
14+
// any effort to be exception-safe and therefore much simpler.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef SWIFT_BASIC_TAGGEDUNION_H
19+
#define SWIFT_BASIC_TAGGEDUNION_H
20+
21+
#include "swift/Basic/ExternalUnion.h"
22+
23+
namespace swift {
24+
25+
namespace TaggedUnionImpl {
26+
template <class T>
27+
using simplify_member_type
28+
= typename std::remove_const<typename std::remove_reference<T>::type>::type;
29+
30+
/// Given a type `T` deduced from a `T &&` parameter, can it be used to
31+
/// construct a member of the union? If so, `simplify_member_type<T>`
32+
/// will give the formal member type.
33+
template <class Members, class T>
34+
inline constexpr bool is_member_constructible() {
35+
return Members::template contains<simplify_member_type<T>>();
36+
}
37+
38+
struct Empty {};
39+
}
40+
41+
template <class KindHelper, class Members,
42+
bool NonTrivial = !Members::Info::is_trivially_copyable,
43+
bool HasVoid = Members::template contains<void>()>
44+
class TaggedUnionBase;
45+
46+
/// The base partial specialization. All the other partial specializations
47+
/// will end up inheriting from this.
48+
template <class KindHelper, class Members>
49+
class TaggedUnionBase<KindHelper, Members,
50+
/*NonTrivial*/ false,
51+
/*HasVoid*/ false> {
52+
protected:
53+
using StorageType = SimpleExternalUnionBase<KindHelper, Members>;
54+
using Kind = typename KindHelper::Kind;
55+
StorageType Storage;
56+
Kind TheKind;
57+
58+
TaggedUnionBase(Kind theKind) : TheKind(theKind) {}
59+
60+
public:
61+
/// Construct the union with a value of the given type, which must
62+
/// (ignoring references) be one of the declared members of the union.
63+
template <class T>
64+
TaggedUnionBase(T &&value,
65+
typename std::enable_if<
66+
TaggedUnionImpl::is_member_constructible<Members, T>(),
67+
TaggedUnionImpl::Empty>::type = {}) {
68+
using TargetType = TaggedUnionImpl::simplify_member_type<T>;
69+
TheKind = StorageType::template kindForMember<TargetType>();
70+
Storage.template emplace<TargetType>(TheKind, std::forward<T>(value));
71+
}
72+
73+
template <class T>
74+
typename std::enable_if<TaggedUnionImpl::is_member_constructible<Members, T>(),
75+
TaggedUnionBase &>::type
76+
operator=(T &&value) {
77+
using TargetType = TaggedUnionImpl::simplify_member_type<T>;
78+
TheKind = StorageType::template kindForMember<TargetType>();
79+
Storage.template emplace<TargetType>(TheKind, std::forward<T>(value));
80+
return *this;
81+
}
82+
83+
/// Replace the current value in the union with a value of the given
84+
/// type, which must be one of the declared members of the union.
85+
///
86+
/// The value will be constructed from the arguments with an argument
87+
/// list (i.e. `new(ptr) T(...)`). If aggregate initialization is required,
88+
/// use `emplaceAggregate`.
89+
template <class T, class... Args>
90+
T &emplace(Args &&... args) {
91+
Storage.destruct(TheKind);
92+
TheKind = StorageType::template kindForMember<T>();
93+
return Storage.template emplace<T>(TheKind, std::forward<Args>(args)...);
94+
}
95+
96+
/// Replace the current value in the union with a value of the given
97+
/// type, which must be one of the declared members of the union.
98+
///
99+
/// The value will be constructed from the arguments with a braced
100+
/// initializer list (i.e. `T{...}`) and therefore may use aggregate
101+
/// initialization.
102+
template <class T, class... Args>
103+
T &emplaceAggregate(Args &&... args) {
104+
Storage.destruct(TheKind);
105+
TheKind = StorageType::template kindForMember<T>();
106+
return Storage.template emplaceAggregate<T>(TheKind,
107+
std::forward<Args>(args)...);
108+
}
109+
110+
/// Return true if the union is storing a value of the given type,
111+
/// which must be one of the declared members of the union.
112+
template <class T>
113+
bool isa() const {
114+
return TheKind == StorageType::template kindForMember<T>();
115+
}
116+
117+
/// Return a pointer to the value if the union is storing a value of the
118+
/// given type, which must be one of the declared members of the union.
119+
template <class T>
120+
T *dyn_cast() {
121+
return Storage.template dyn_cast<T>(TheKind);
122+
}
123+
124+
/// Return a pointer to the value if the union is storing a value of the
125+
/// given type, which must be one of the declared members of the union.
126+
template <class T>
127+
const T *dyn_cast() const {
128+
return Storage.template dyn_cast<T>(TheKind);
129+
}
130+
131+
/// Assert that the union is storing a value of the given type and return
132+
/// a reference to it.
133+
template <class T>
134+
T &get() {
135+
return Storage.template get<T>(TheKind);
136+
}
137+
138+
/// Assert that the union is storing a value of the given type and return
139+
/// a reference to it.
140+
template <class T>
141+
const T &get() const {
142+
return Storage.template get<T>(TheKind);
143+
}
144+
};
145+
146+
/// The partial specialization for when the union isn't trivially-copyable.
147+
template <class KindHelper, class Members>
148+
class TaggedUnionBase<KindHelper, Members, /*NonTrivial*/ true, /*HasVoid*/ false>
149+
: public TaggedUnionBase<KindHelper, Members, false, false> {
150+
using super = TaggedUnionBase<KindHelper, Members, false, false>;
151+
152+
protected:
153+
using super::Storage;
154+
using super::TheKind;
155+
156+
TaggedUnionBase(typename super::Kind kind) : super(kind) {}
157+
158+
public:
159+
template <class T>
160+
TaggedUnionBase(T &&value,
161+
typename std::enable_if<
162+
TaggedUnionImpl::is_member_constructible<Members, T>(),
163+
TaggedUnionImpl::Empty>::type = {})
164+
: super(std::forward<T>(value)) {}
165+
166+
TaggedUnionBase(const TaggedUnionBase &other) : super(other.TheKind) {
167+
Storage.copyConstruct(other.TheKind, other.Storage);
168+
}
169+
170+
TaggedUnionBase(TaggedUnionBase &&other) : super(other.TheKind) {
171+
Storage.moveConstruct(other.TheKind, std::move(other.Storage));
172+
}
173+
174+
TaggedUnionBase &operator=(const TaggedUnionBase &other) {
175+
Storage.copyAssign(TheKind, other.TheKind, other.Storage);
176+
TheKind = other.TheKind;
177+
return *this;
178+
}
179+
180+
TaggedUnionBase &operator=(TaggedUnionBase &&other) {
181+
Storage.moveAssign(TheKind, other.TheKind, std::move(other.Storage));
182+
TheKind = other.TheKind;
183+
return *this;
184+
}
185+
186+
~TaggedUnionBase() {
187+
Storage.destruct(TheKind);
188+
}
189+
};
190+
191+
/// The partial specialization for when `void` is a member of the union.
192+
template <class KindHelper, class Members, bool NonTrivial>
193+
class TaggedUnionBase<KindHelper, Members, NonTrivial, /*HasVoid*/ true>
194+
: public TaggedUnionBase<KindHelper, Members, NonTrivial, false> {
195+
using super = TaggedUnionBase<KindHelper, Members, NonTrivial, false>;
196+
197+
protected:
198+
using super::Storage;
199+
using super::TheKind;
200+
201+
static constexpr typename super::Kind kindForVoid() {
202+
return super::StorageType::template kindForMember<void>();
203+
}
204+
205+
public:
206+
template <class T>
207+
TaggedUnionBase(T &&value,
208+
typename std::enable_if<
209+
TaggedUnionImpl::is_member_constructible<Members, T>(),
210+
TaggedUnionImpl::Empty>::type = {})
211+
: super(std::forward<T>(value)) {}
212+
213+
/// Construct the union in the empty state.
214+
TaggedUnionBase() : super(kindForVoid()) {}
215+
216+
/// Test whether the union is in the empty state.
217+
bool empty() const {
218+
return TheKind == kindForVoid();
219+
}
220+
221+
/// Reset the union to the empty state.
222+
void reset() {
223+
Storage.destruct(TheKind);
224+
TheKind = kindForVoid();
225+
}
226+
};
227+
228+
/// A tagged union of the given types.
229+
///
230+
/// Non-trivial members are supported; they will make the union
231+
/// non-trivial to copy, move, and destruct.
232+
///
233+
/// The union provides the following members, as described in the
234+
/// documentation for the primary partial specialization of
235+
/// TaggedUnionBase. In the following, `M` must be a declared member
236+
/// type of the union.
237+
///
238+
/// ```
239+
/// TaggedUnion(M);
240+
/// M &emplace<M>(T...);
241+
/// M &emplaceAggregate<M>(T...);
242+
/// bool isa<M>() const;
243+
/// M *dyn_cast<M>();
244+
/// const M *dyn_cast<M>() const;
245+
/// M &get<M>();
246+
/// const M &get<M>() const;
247+
/// ```
248+
///
249+
/// Additionally, if `void` is one of the types, the union supports an
250+
/// empty state and provides a default constructor as well as `empty()`
251+
/// and `reset()` methods.
252+
template <class... Members>
253+
using TaggedUnion =
254+
TaggedUnionBase<ExternalUnionImpl::OptimalKindTypeHelper<sizeof...(Members)>,
255+
ExternalUnionMembers<Members...>>;
256+
257+
}
258+
259+
#endif

unittests/Basic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ add_swift_unittest(SwiftBasicTests
2626
STLExtrasTest.cpp
2727
StringExtrasTest.cpp
2828
SuccessorMapTest.cpp
29+
TaggedUnionTest.cpp
2930
ThreadSafeRefCntPointerTest.cpp
3031
TransformRangeTest.cpp
3132
TreeScopedHashTableTest.cpp

0 commit comments

Comments
 (0)