Skip to content

Commit

Permalink
Makes variant<Ts...> hashable iff Ts... are hashable, closes #125
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-j-h committed Nov 18, 2016
1 parent 9a115c5 commit 97d0379
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 out/lambda_overload_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
Expand Down Expand Up @@ -51,6 +51,10 @@ 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
Expand Down
22 changes: 22 additions & 0 deletions include/mapbox/variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <functional>

#include <mapbox/recursive_wrapper.hpp>

Expand Down Expand Up @@ -531,6 +532,16 @@ class comparer
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
Expand Down Expand Up @@ -971,4 +982,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
158 changes: 158 additions & 0 deletions test/hashable_test.cpp
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit 97d0379

Please sign in to comment.