Bug 1186693 - Add exhaustive matching to mozilla::Variant; r=Waldo

This commit is contained in:
Nick Fitzgerald 2015-08-08 16:43:35 -07:00
parent 6de4674783
commit 26eaf483ae
2 changed files with 117 additions and 2 deletions

View File

@ -117,10 +117,15 @@ struct VariantImplementation<N, T> {
template<typename Variant>
static bool
equal(const Variant& aLhs, const Variant& aRhs)
{
equal(const Variant& aLhs, const Variant& aRhs) {
return aLhs.template as<T>() == aRhs.template as<T>();
}
template<typename Matcher, typename ConcreteVariant>
static typename Matcher::ReturnType
match(Matcher& aMatcher, ConcreteVariant& aV) {
return aMatcher.match(aV.template as<T>());
}
};
// VariantImplementation for some variant type T.
@ -171,6 +176,27 @@ struct VariantImplementation<N, T, Ts...>
return Next::equal(aLhs, aRhs);
}
}
template<typename Matcher, typename ConcreteVariant>
static typename Matcher::ReturnType
match(Matcher& aMatcher, ConcreteVariant& aV)
{
if (aV.template is<T>()) {
return aMatcher.match(aV.template as<T>());
} else {
// If you're seeing compilation errors here like "no matching
// function for call to 'match'" then that means that the
// Matcher doesn't exhaust all variant types. There must exist a
// Matcher::match(T&) for every variant type T.
//
// If you're seeing compilation errors here like "cannot
// initialize return object of type <...> with an rvalue of type
// <...>" then that means that the Matcher::match(T&) overloads
// are returning different types. They must all return the same
// Matcher::ReturnType type.
return Next::match(aMatcher, aV);
}
}
};
} // namespace detail
@ -229,6 +255,36 @@ struct VariantImplementation<N, T, Ts...>
* Variant<UniquePtr<A>, B, C> v(MakeUnique<A>());
* auto ptr = v.extract<UniquePtr<A>>();
*
* Finally, you can exhaustively match on the contained variant and branch into
* different code paths depending which type is contained. This is preferred to
* manually checking every variant type T with is<T>() because it provides
* compile-time checking that you handled every type, rather than runtime
* assertion failures.
*
* // Bad!
* char* foo(Variant<A, B, C, D>& v) {
* if (v.is<A>()) {
* return ...;
* } else if (v.is<B>()) {
* return ...;
* } else {
* return doSomething(v.as<C>()); // Forgot about case D!
* }
* }
*
* // Good!
* struct FooMatcher
* {
* using ReturnType = char*;
* ReturnType match(A& a) { ... }
* ReturnType match(B& b) { ... }
* ReturnType match(C& c) { ... }
* ReturnType match(D& d) { ... } // Compile-time error to forget D!
* }
* char* foo(Variant<A, B, C, D>& v) {
* return v.match(FooMatcher());
* }
*
* ## Examples
*
* A tree is either an empty leaf, or a node with a value and two children:
@ -381,6 +437,22 @@ public:
MOZ_ASSERT(is<T>());
return T(Move(as<T>()));
}
// Exhaustive matching of all variant types no the contained value.
/** Match on an immutable const reference. */
template<typename Matcher>
typename Matcher::ReturnType
match(Matcher& aMatcher) const {
return Impl::match(aMatcher, *this);
}
/** Match on a mutable non-const reference. */
template<typename Matcher>
typename Matcher::ReturnType
match(Matcher& aMatcher) {
return Impl::match(aMatcher, *this);
}
};
} // namespace mozilla

View File

@ -123,6 +123,48 @@ testEquality()
MOZ_RELEASE_ASSERT(v6 == v6);
}
struct Describer
{
static const char* little;
static const char* medium;
static const char* big;
using ReturnType = const char*;
const char* match(const uint8_t&) { return little; }
const char* match(const uint32_t&) { return medium; }
const char* match(const uint64_t&) { return big; }
};
const char* Describer::little = "little";
const char* Describer::medium = "medium";
const char* Describer::big = "big";
static void
testMatching()
{
printf("testMatching\n");
using V = Variant<uint8_t, uint32_t, uint64_t>;
Describer desc;
V v1(uint8_t(1));
V v2(uint32_t(2));
V v3(uint64_t(3));
MOZ_RELEASE_ASSERT(v1.match(desc) == Describer::little);
MOZ_RELEASE_ASSERT(v2.match(desc) == Describer::medium);
MOZ_RELEASE_ASSERT(v3.match(desc) == Describer::big);
const V& constRef1 = v1;
const V& constRef2 = v2;
const V& constRef3 = v3;
MOZ_RELEASE_ASSERT(constRef1.match(desc) == Describer::little);
MOZ_RELEASE_ASSERT(constRef2.match(desc) == Describer::medium);
MOZ_RELEASE_ASSERT(constRef3.match(desc) == Describer::big);
}
int
main()
{
@ -131,6 +173,7 @@ main()
testMove();
testDestructor();
testEquality();
testMatching();
printf("TestVariant OK!\n");
return 0;