Commit cc84c39c authored by Nick Terrell's avatar Nick Terrell Committed by Facebook Github Bot

Path variants for get_(ref_)default()

Summary: Useful for the same reason as the `get_ptr()` path function, but when you want to use a default value.

Reviewed By: luciang, yfeldblum

Differential Revision: D4785728

fbshipit-source-id: 70fd56f9ffa7a9edd6740f6dd712d3a251bf9fb0
parent 89736661
......@@ -18,6 +18,7 @@
#include <folly/Conv.h>
#include <folly/Optional.h>
#include <tuple>
namespace folly {
......@@ -166,6 +167,8 @@ typename Map::mapped_type* get_ptr(
return (pos != map.end() ? &pos->second : nullptr);
}
// TODO: Remove the return type computations when clang 3.5 and gcc 5.1 are
// the minimum supported versions.
namespace detail {
template <
class T,
......@@ -179,6 +182,25 @@ template <class T>
struct NestedMapType<T, 1> {
using type = typename T::mapped_type;
};
template <typename... KeysDefault>
struct DefaultType;
template <typename Default>
struct DefaultType<Default> {
using type = Default;
};
template <typename Key, typename... KeysDefault>
struct DefaultType<Key, KeysDefault...> {
using type = typename DefaultType<KeysDefault...>::type;
};
template <class... KeysDefault>
auto extract_default(const KeysDefault&... keysDefault) ->
typename DefaultType<KeysDefault...>::type const& {
return std::get<sizeof...(KeysDefault)-1>(std::tie(keysDefault...));
}
}
/**
......@@ -203,4 +225,53 @@ auto get_ptr(Map& map, const Key1& key1, const Key2& key2, const Keys&... keys)
return pos != map.end() ? get_ptr(pos->second, key2, keys...) : nullptr;
}
/**
* Given a map and a path of keys, return the value corresponding to the nested
* value, or a given default value if the path doesn't exist in the map.
* The default value is the last parameter, and is copied when returned.
*/
template <
class Map,
class Key1,
class Key2,
class... KeysDefault,
typename = typename std::enable_if<sizeof...(KeysDefault) != 0>::type>
auto get_default(
const Map& map,
const Key1& key1,
const Key2& key2,
const KeysDefault&... keysDefault) ->
typename detail::NestedMapType<Map, 1 + sizeof...(KeysDefault)>::type {
if (const auto* ptr = get_ptr(map, key1)) {
return get_default(*ptr, key2, keysDefault...);
}
return detail::extract_default(keysDefault...);
}
/**
* Given a map and a path of keys, return a reference to the value corresponding
* to the nested value, or the given default reference if the path doesn't exist
* in the map.
* The default value is the last parameter, and must be a lvalue reference.
*/
template <
class Map,
class Key1,
class Key2,
class... KeysDefault,
typename = typename std::enable_if<sizeof...(KeysDefault) != 0>::type,
typename = typename std::enable_if<std::is_lvalue_reference<
typename detail::DefaultType<KeysDefault...>::type>::value>::type>
auto get_ref_default(
const Map& map,
const Key1& key1,
const Key2& key2,
KeysDefault&&... keysDefault) ->
typename detail::NestedMapType<Map, 1 + sizeof...(KeysDefault)>::type
const& {
if (const auto* ptr = get_ptr(map, key1)) {
return get_ref_default(*ptr, key2, keysDefault...);
}
return detail::extract_default(keysDefault...);
}
} // namespace folly
......@@ -141,6 +141,19 @@ TEST(MapUtil, get_ptr_path_mixed) {
}
namespace {
template <typename T>
struct element_type {
using type = typename std::decay<T>::type;
};
template <typename T>
struct element_type<T()> {
using type = T;
};
template <typename T>
using element_type_t = typename element_type<T>::type;
template <typename T, typename = void>
struct Compiles : std::false_type {};
......@@ -148,7 +161,7 @@ template <typename T>
struct Compiles<
T,
void_t<decltype(get_ref_default(
std::declval<std::map<int, typename std::decay<T>::type>>(),
std::declval<std::map<int, element_type_t<T>>>(),
std::declval<int>(),
std::declval<T>()))>> : std::true_type {};
}
......@@ -158,4 +171,78 @@ TEST(MapUtil, get_default_temporary) {
EXPECT_TRUE(Compiles<int&>::value);
EXPECT_FALSE(Compiles<const int&&>::value);
EXPECT_FALSE(Compiles<int&&>::value);
EXPECT_TRUE(Compiles<const int&()>::value);
EXPECT_TRUE(Compiles<int&()>::value);
EXPECT_FALSE(Compiles<int()>::value);
}
TEST(MapUtil, get_default_path) {
using std::map;
map<int, map<int, int>> m;
m[4][2] = 42;
EXPECT_EQ(42, get_default(m, 4, 2, 42));
EXPECT_EQ(42, get_default(m, 1, 3, 42));
}
TEST(MapUtil, get_default_path_mixed) {
using std::map;
using std::unordered_map;
using std::string;
map<int, unordered_map<string, StringPiece>> m;
int key1 = 42;
const string key2 = "hello";
constexpr StringPiece value = "world";
constexpr StringPiece dflt = "default";
m[key1][key2] = value;
EXPECT_EQ(value, get_default(m, 42, key2, dflt));
EXPECT_EQ(value, get_default(m, key1, "hello", dflt));
EXPECT_EQ(dflt, get_default(m, 0, key2, dflt));
EXPECT_EQ(dflt, get_default(m, key1, "bad", "default"));
}
TEST(MapUtil, get_ref_default_path) {
using std::map;
map<int, map<int, int>> m;
m[4][2] = 42;
const int dflt = 13;
EXPECT_EQ(42, get_ref_default(m, 4, 2, dflt));
EXPECT_EQ(dflt, get_ref_default(m, 1, 3, dflt));
}
TEST(MapUtil, get_ref_default_path_mixed) {
using std::map;
using std::unordered_map;
using std::string;
map<int, unordered_map<string, StringPiece>> m;
int key1 = 42;
const string key2 = "hello";
constexpr StringPiece value = "world";
constexpr StringPiece dflt = "default";
m[key1][key2] = value;
EXPECT_EQ(value, get_ref_default(m, 42, key2, dflt));
EXPECT_EQ(value, get_ref_default(m, key1, "hello", dflt));
EXPECT_EQ(dflt, get_ref_default(m, 0, key2, dflt));
EXPECT_EQ(dflt, get_ref_default(m, key1, "bad", dflt));
}
namespace {
template <typename T, typename = void>
struct GetRefDefaultPathCompiles : std::false_type {};
template <typename T>
struct GetRefDefaultPathCompiles<
T,
void_t<decltype(get_ref_default(
std::declval<std::map<int, std::map<int, element_type_t<T>>>>(),
std::declval<int>(),
std::declval<int>(),
std::declval<T>()))>> : std::true_type {};
}
TEST(MapUtil, get_ref_default_path_temporary) {
EXPECT_TRUE(GetRefDefaultPathCompiles<const int&>::value);
EXPECT_TRUE(GetRefDefaultPathCompiles<int&>::value);
EXPECT_FALSE(GetRefDefaultPathCompiles<const int&&>::value);
EXPECT_FALSE(GetRefDefaultPathCompiles<int&&>::value);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment