This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository mapbox-variant.
commit a7491b3ad547d55c8339275894bc7ab26af2474a Author: Bas Couwenberg <[email protected]> Date: Wed Nov 23 17:19:12 2016 +0100 Imported Upstream version 1.1.4 --- Makefile | 10 ++- README.md | 168 +++++++++++++++++++++++++++++++++++-- include/mapbox/variant.hpp | 39 +++++++++ include/mapbox/variant_visitor.hpp | 38 +++++++++ test/hashable_test.cpp | 158 ++++++++++++++++++++++++++++++++++ test/lambda_overload_test.cpp | 127 ++++++++++++++++++++++++++++ 6 files changed, 533 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index fc554b8..10ab7b5 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ LDFLAGS := $(LDFLAGS) ALL_HEADERS = $(shell find include/mapbox/ '(' -name '*.hpp' ')') -all: out/bench-variant out/unique_ptr_test out/unique_ptr_test out/recursive_wrapper_test out/binary_visitor_test +all: out/bench-variant out/unique_ptr_test out/unique_ptr_test out/recursive_wrapper_test out/binary_visitor_test out/lambda_overload_test out/hashable_test mason_packages: git submodule update --init .mason @@ -47,6 +47,14 @@ out/binary_visitor_test: Makefile mason_packages test/binary_visitor_test.cpp mkdir -p ./out $(CXX) -o out/binary_visitor_test test/binary_visitor_test.cpp -I./include -Itest/include $(RELEASE_FLAGS) $(COMMON_FLAGS) $(CXXFLAGS) $(LDFLAGS) $(BOOST_FLAGS) +out/lambda_overload_test: Makefile mason_packages test/lambda_overload_test.cpp + mkdir -p ./out + $(CXX) -o out/lambda_overload_test test/lambda_overload_test.cpp -I./include -Itest/include $(RELEASE_FLAGS) $(COMMON_FLAGS) $(CXXFLAGS) $(LDFLAGS) $(BOOST_FLAGS) + +out/hashable_test: Makefile mason_packages test/hashable_test.cpp + mkdir -p ./out + $(CXX) -o out/hashable_test test/hashable_test.cpp -I./include -Itest/include $(RELEASE_FLAGS) $(COMMON_FLAGS) $(CXXFLAGS) $(LDFLAGS) $(BOOST_FLAGS) + bench: out/bench-variant out/unique_ptr_test out/unique_ptr_test out/recursive_wrapper_test out/binary_visitor_test ./out/bench-variant 100000 ./out/unique_ptr_test 100000 diff --git a/README.md b/README.md index 1de54de..0e274b0 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,169 @@ # Mapbox Variant -An alternative to `boost::variant` for C++11 and C++14 +An header-only alternative to `boost::variant` for C++11 and C++14 [](https://travis-ci.org/mapbox/variant) [](https://ci.appveyor.com/project/Mapbox/variant) [](https://coveralls.io/r/mapbox/variant?branch=master) +## Introduction + +Variant's basic building blocks are: +- `variant<Ts...>` - a type-safe representation for sum-types / discriminated unions +- `recursive_wrapper<T>` - a helper type to represent recursive "tree-like" variants +- `apply_visitor(visitor, myVariant)` - to invoke a custom visitor on the variant's underlying type +- `get<T>()` - a function to directly unwrap a variant's underlying type +- `.match([](Type){})` - a variant convenience member function taking an arbitrary number of lambdas creating a visitor behind the scenes and applying it to the variant + + +### Basic Usage - HTTP API Example + +Suppose you want to represent a HTTP API response which is either a JSON result or an error: + +```c++ +struct Result { + Json object; +}; + +struct Error { + int32_t code; + string message; +}; +``` + +You can represent this at type level using a variant which is either an `Error` or a `Result`: + +```c++ +using Response = variant<Error, Result>; + +Response makeRequest() { + return Error{501, "Not Implemented"}; +} + +Response ret = makeRequest(); +``` + +To see which type the `Response` holds you pattern match on the variant unwrapping the underlying value: + +```c++ +ret.match([] (Result r) { print(r.object); } + [] (Error e) { print(e.message); }); +``` + +Instead of using the variant's convenience `.match` pattern matching function you can create a type visitor functor and use `apply_visitor` manually: + +```c++ +struct ResponseVisitor { + void operator()(Result r) const { + print(r.object); + } + + void operator()(Error e) const { + print(e.message); + } +}; + +ResponseVisitor visitor; +apply_visitor(visitor, ret); +``` + +In both cases the compiler makes sure you handle all types the variant can represent at compile. + + +### Recursive Variants - JSON Example + +[JSON](http://www.json.org/) consists of types `String`, `Number`, `True`, `False`, `Null`, `Array` and `Object`. + + +```c++ +struct String { string value; }; +struct Number { double value; }; +struct True { }; +struct False { }; +struct Null { }; +struct Array { vector<?> values; }; +struct Object { unordered_map<string, ?> values; }; +``` + +This works for primitive types but how do we represent recursive types such as `Array` which can hold multiple elements and `Array` itself, too? + +For these use cases Variant provides a `recursive_wrapper` helper type which lets you express recursive Variants. + +```c++ +struct String { string value; }; +struct Number { double value; }; +struct True { }; +struct False { }; +struct Null { }; + +// Forward declarations only +struct Array; +struct Object; + +using Value = variant<String, Number, True, False, Null, recursive_wrapper<Array>, recursive_wrapper<Object>>; + +struct Array { + vector<Value> values; +}; + +struct Object { + unordered_map<string, Value> values; +}; +``` + +For walkig the JSON representation you can again either create a `JSONVisitor`: + +```c++ +struct JSONVisitor { + + void operator()(Null) const { + print("null"); + } + + // same for all other JSON types +}; + +JSONVisitor visitor; +apply_visitor(visitor, json); +``` + +Or use the convenience `.match` pattern matching function: + +```c++ +json.match([] (Null) { print("null"); }, + ...); +``` + +To summarize: use `recursive_wrapper` to represent recursive "tree-like" representations: + +```c++ +struct Empty { }; +struct Node; + +using Tree = variant<Empty, recursive_wrapper<Node>>; + +struct Node { + uint64_t value; +} +``` +### Advanced Usage Tips + +Creating type aliases for variants is a great way to reduce repetition. +Keep in mind those type aliases are not checked at type level, though. +We recommend creating a new type for all but basic variant usage: + +```c++ +// the compiler can't tell the following two apart +using APIResult = variant<Error, Result>; +using FilesystemResult = variant<Error, Result>; + +// new type +struct APIResult : variant<Error, Result> { + using Base = variant<Error, Result>; + using Base::Base; +} +``` + ## Why use Mapbox Variant? @@ -45,6 +203,9 @@ implementations](doc/other_implementations.md). Want to know more about the upcoming standard? Have a look at our [overview](doc/standards_effort.md). +Most modern high-level languages provide ways to express sum types directly. +If you're curious have a look at Haskell's pattern matching or Rust's and Swift's enums. + ## Depends @@ -63,11 +224,6 @@ Tested with: - clang++-3.9 - Visual Studio 2015 -## Usage - -There is nothing to build, just include `variant.hpp` in your project. Include `variant_io.hpp` if you need -the `operator<<` overload for variant. - ## Unit Tests diff --git a/include/mapbox/variant.hpp b/include/mapbox/variant.hpp index afdf36a..fb0f77e 100644 --- a/include/mapbox/variant.hpp +++ b/include/mapbox/variant.hpp @@ -10,8 +10,10 @@ #include <type_traits> #include <typeinfo> #include <utility> +#include <functional> #include <mapbox/recursive_wrapper.hpp> +#include <mapbox/variant_visitor.hpp> // clang-format off // [[deprecated]] is only available in C++14, use this for the time being @@ -531,6 +533,16 @@ private: Variant const& lhs_; }; +// hashing visitor +struct hasher +{ + template <typename T> + std::size_t operator()(const T& hashable) const + { + return std::hash<T>{}(hashable); + } +}; + } // namespace detail struct no_init @@ -861,6 +873,22 @@ public: return detail::binary_dispatcher<F, V, R, Types...>::apply(v0, v1, std::forward<F>(f)); } + // match + // unary + template <typename... Fs> + auto VARIANT_INLINE match(Fs&&... fs) const + -> decltype(variant::visit(*this, ::mapbox::util::make_visitor(std::forward<Fs>(fs)...))) + { + return variant::visit(*this, ::mapbox::util::make_visitor(std::forward<Fs>(fs)...)); + } + // non-const + template <typename... Fs> + auto VARIANT_INLINE match(Fs&&... fs) + -> decltype(variant::visit(*this, ::mapbox::util::make_visitor(std::forward<Fs>(fs)...))) + { + return variant::visit(*this, ::mapbox::util::make_visitor(std::forward<Fs>(fs)...)); + } + ~variant() noexcept // no-throw destructor { helper_type::destroy(type_index, &data); @@ -971,4 +999,15 @@ ResultType const& get_unchecked(T const& var) } // namespace util } // namespace mapbox +// hashable iff underlying types are hashable +namespace std { +template <typename... Types> +struct hash< ::mapbox::util::variant<Types...>> { + std::size_t operator()(const ::mapbox::util::variant<Types...>& v) const noexcept + { + return ::mapbox::util::apply_visitor(::mapbox::util::detail::hasher{}, v); + } +}; +} + #endif // MAPBOX_UTIL_VARIANT_HPP diff --git a/include/mapbox/variant_visitor.hpp b/include/mapbox/variant_visitor.hpp new file mode 100644 index 0000000..481eb65 --- /dev/null +++ b/include/mapbox/variant_visitor.hpp @@ -0,0 +1,38 @@ +#ifndef MAPBOX_UTIL_VARIANT_VISITOR_HPP +#define MAPBOX_UTIL_VARIANT_VISITOR_HPP + +namespace mapbox { +namespace util { + +template <typename... Fns> +struct visitor; + +template <typename Fn> +struct visitor<Fn> : Fn +{ + using type = Fn; + using Fn::operator(); + + visitor(Fn fn) : Fn(fn) {} +}; + +template <typename Fn, typename... Fns> +struct visitor<Fn, Fns...> : Fn, visitor<Fns...> +{ + using type = visitor; + using Fn::operator(); + using visitor<Fns...>::operator(); + + visitor(Fn fn, Fns... fns) : Fn(fn), visitor<Fns...>(fns...) {} +}; + +template <typename... Fns> +visitor<Fns...> make_visitor(Fns... fns) +{ + return visitor<Fns...>(fns...); +} + +} // namespace util +} // namespace mapbox + +#endif // MAPBOX_UTIL_VARIANT_VISITOR_HPP diff --git a/test/hashable_test.cpp b/test/hashable_test.cpp new file mode 100644 index 0000000..f3393a9 --- /dev/null +++ b/test/hashable_test.cpp @@ -0,0 +1,158 @@ +#include <cstddef> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <string> +#include <unordered_set> +#include <utility> + +#include <mapbox/variant.hpp> + +using namespace mapbox::util; + +void test_singleton() +{ + using V = variant<int>; + + V singleton = 5; + + if (std::hash<V>{}(singleton) != std::hash<int>{}(5)) + { + std::cerr << "Expected variant hash to be the same as hash of its value\n"; + std::exit(EXIT_FAILURE); + } +} + +void test_default_hashable() +{ + using V = variant<int, double, std::string>; + + V var; + + // Check int hashes + var = 1; + + if (std::hash<V>{}(var) != std::hash<int>{}(1)) + { + std::cerr << "Expected variant hash to be the same as hash of its value\n"; + std::exit(EXIT_FAILURE); + } + + // Check double hashes + var = 23.4; + + if (std::hash<V>{}(var) != std::hash<double>{}(23.4)) + { + std::cerr << "Expected variant hash to be the same as hash of its value\n"; + std::exit(EXIT_FAILURE); + } + + // Check string hashes + var = std::string{"Hello, World!"}; + + if (std::hash<V>{}(var) != std::hash<std::string>{}("Hello, World!")) + { + std::cerr << "Expected variant hash to be the same as hash of its value\n"; + std::exit(EXIT_FAILURE); + } +} + +struct Hashable +{ + static const constexpr auto const_hash = 5; +}; + +namespace std { +template <> +struct hash<Hashable> +{ + std::size_t operator()(const Hashable&) const noexcept + { + return Hashable::const_hash; + } +}; +} + +void test_custom_hasher() +{ + using V = variant<int, Hashable, double>; + + V var; + + var = Hashable{}; + + if (std::hash<V>{}(var) != Hashable::const_hash) + { + std::cerr << "Expected variant hash to be the same as hash of its value\n"; + std::exit(EXIT_FAILURE); + } +} + +void test_hashable_in_container() +{ + using V = variant<int, std::string, double>; + + // won't compile if V is not Hashable + std::unordered_set<V> vs; + + vs.insert(1); + vs.insert(2.3); + vs.insert("4"); +} + +struct Empty +{ +}; + +struct Node; + +using Tree = variant<Empty, recursive_wrapper<Node>>; + +struct Node +{ + Node(Tree left_, Tree right_) : left(std::move(left_)), right(std::move(right_)) {} + + Tree left = Empty{}; + Tree right = Empty{}; +}; + +namespace std { +template <> +struct hash<Empty> +{ + std::size_t operator()(const Empty&) const noexcept + { + return 3; + } +}; + +template <> +struct hash<Node> +{ + std::size_t operator()(const Node& n) const noexcept + { + return 5 + std::hash<Tree>{}(n.left) + std::hash<Tree>{}(n.right); + } +}; +} + +void test_recursive_hashable() +{ + + Tree tree = Node{Node{Empty{}, Empty{}}, Empty{}}; + + if (std::hash<Tree>{}(tree) != ((5 + (5 + (3 + 3))) + 3)) + { + std::cerr << "Expected variant hash to be the same as hash of its value\n"; + std::exit(EXIT_FAILURE); + } +} + +int main() +{ + test_singleton(); + test_default_hashable(); + test_custom_hasher(); + test_hashable_in_container(); + test_recursive_hashable(); +} diff --git a/test/lambda_overload_test.cpp b/test/lambda_overload_test.cpp new file mode 100644 index 0000000..9e76d99 --- /dev/null +++ b/test/lambda_overload_test.cpp @@ -0,0 +1,127 @@ +#include <iostream> +#include <vector> + +#include <mapbox/variant.hpp> +#include <mapbox/variant_visitor.hpp> + +#if __cplusplus >= 201402L +#define HAS_CPP14_SUPPORT +#endif + +using namespace mapbox::util; + +template <typename Left, typename Right> +using Either = mapbox::util::variant<Left, Right>; + +struct Response +{ +}; + +struct Error +{ +}; + +void test_lambda_overloads() +{ + Either<Error, Response> rv; + + rv = Response{}; + + auto visitor = make_visitor([](Response) { std::cout << "Response\n"; }, // + [](Error) { std::cout << "Error\n"; }); // + apply_visitor(visitor, rv); +} + +void test_lambda_overloads_capture() +{ + Either<Error, Response> rv; + + rv = Error{}; + + int ok = 0; + int err = 0; + + auto visitor = make_visitor([&](Response) { ok += 1; }, // + [&](Error) { err += 1; }); // + apply_visitor(visitor, rv); + + std::cout << "Got " << ok << " ok, " << err << " err" << std::endl; +} + +void test_singleton_variant() +{ + + variant<int> singleton; + apply_visitor(make_visitor([](int) {}), singleton); +} + +void test_lambda_overloads_sfinae() +#ifdef HAS_CPP14_SUPPORT +{ + variant<int, float, std::vector<int>> var; + + auto visitor = make_visitor([](auto range) -> decltype(std::begin(range), void()) { + for (auto each : range) + std::cout << each << ' '; }, + [](auto x) -> decltype(std::cout << x, void()) { + std::cout << x << std::endl; + }); + + var = 1; + apply_visitor(visitor, var); + + var = 2.f; + apply_visitor(visitor, var); + + var = std::vector<int>{4, 5, 6}; + apply_visitor(visitor, var); +} +#else +{ +} +#endif + +void test_match_singleton() +{ + variant<int> singleton = 5; + singleton.match([](int) {}); +} + +void test_match_overloads() +{ + Either<Error, Response> rv; + + rv = Response{}; + + rv.match([](Response) { std::cout << "Response\n"; }, // + [](Error) { std::cout << "Error\n"; }); // +} + +void test_match_overloads_capture() +{ + Either<Error, Response> rv; + + rv = Error{}; + + int ok = 0; + int err = 0; + + rv.match([&](Response) { ok += 1; }, // + [&](Error) { err += 1; }); // + + std::cout << "Got " << ok << " ok, " << err << " err" << std::endl; +} + +int main() +{ + test_lambda_overloads(); + test_singleton_variant(); + test_lambda_overloads_capture(); + test_lambda_overloads_sfinae(); + + test_match_singleton(); + test_match_overloads(); + test_match_overloads_capture(); +} + +#undef HAS_CPP14_SUPPORT -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mapbox-variant.git _______________________________________________ Pkg-grass-devel mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

