Commit e89b8aa9 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Fix for_each and fetch unit-tests under MSVC

Summary: [Folly] Refactor `for_each` and `fetch` to fix their unit-tests under MSVC. New code is smaller.

Reviewed By: Orvid

Differential Revision: D9569412

fbshipit-source-id: 87807ddf41f1dbd16244a82c5be30eae08dbdf4c
parent 8f2ad2b9
...@@ -427,8 +427,7 @@ if (BUILD_TESTS) ...@@ -427,8 +427,7 @@ if (BUILD_TESTS)
TEST f14_fwd_test SOURCES F14FwdTest.cpp TEST f14_fwd_test SOURCES F14FwdTest.cpp
TEST f14_map_test SOURCES F14MapTest.cpp TEST f14_map_test SOURCES F14MapTest.cpp
TEST f14_set_test SOURCES F14SetTest.cpp TEST f14_set_test SOURCES F14SetTest.cpp
TEST foreach_test WINDOWS_DISABLED TEST foreach_test SOURCES ForeachTest.cpp
SOURCES ForeachTest.cpp
TEST merge_test SOURCES MergeTest.cpp TEST merge_test SOURCES MergeTest.cpp
TEST sparse_byte_set_test SOURCES SparseByteSetTest.cpp TEST sparse_byte_set_test SOURCES SparseByteSetTest.cpp
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/Traits.h> #include <folly/Traits.h>
#include <folly/Utility.h> #include <folly/Utility.h>
#include <folly/functional/Invoke.h>
namespace folly { namespace folly {
...@@ -60,48 +61,50 @@ auto adl_end(Type&& instance) -> decltype(end(instance)) { ...@@ -60,48 +61,50 @@ auto adl_end(Type&& instance) -> decltype(end(instance)) {
} // namespace adl } // namespace adl
/**
* Enable if the tuple supports fetching via non member get<>()
*/
template <typename T>
using EnableIfNonMemberGetFound =
void_t<decltype(adl::adl_get<0>(std::declval<T>()))>;
/** /**
* Enable if the tuple supports fetching via a member get<>() * Enable if the tuple supports fetching via a member get<>()
*/ */
template <typename T> template <typename T>
using EnableIfMemberGetFound = using EnableIfMemberGetFound =
void_t<decltype(std::declval<T>().template get<0>())>; void_t<decltype(std::declval<T>().template get<0>())>;
template <typename, typename T>
struct IsMemberGetFound : std::false_type {};
template <typename T>
struct IsMemberGetFound<EnableIfMemberGetFound<T>, T> : std::true_type {};
/** /**
* A get that tries member get<> first and if that is not found tries ADL get<>. * A get that tries member get<> first and if that is not found tries ADL get<>.
* This mechanism is as found in the structured bindings proposal here 11.5.3. * This mechanism is as found in the structured bindings proposal here 11.5.3.
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf
*/ */
template <std::size_t Index, typename Type, typename = void> template <
struct Get { std::size_t Index,
template <typename T> typename Type,
static auto impl(T&& instance) std::enable_if_t<!IsMemberGetFound<void, Type>::value, int> = 0>
-> decltype(adl::adl_get<Index>(std::declval<T>())) { auto get_impl(Type&& instance)
return adl::adl_get<Index>(std::forward<T>(instance)); -> decltype(adl::adl_get<Index>(static_cast<Type&&>(instance))) {
} return adl::adl_get<Index>(static_cast<Type&&>(instance));
}; }
template <std::size_t Index, typename Type> template <
struct Get<Index, Type, EnableIfMemberGetFound<Type>> { std::size_t Index,
template <typename T> typename Type,
static auto impl(T&& instance) std::enable_if_t<IsMemberGetFound<void, Type>::value, int> = 0>
-> decltype(std::declval<T>().template get<Index>()) { auto get_impl(Type&& instance)
return std::forward<T>(instance).template get<Index>(); -> decltype(static_cast<Type&&>(instance).template get<Index>()) {
} return static_cast<Type&&>(instance).template get<Index>();
}; }
/** /**
* Check if the sequence is a tuple * Check if the sequence is a tuple
*/ */
template <typename Type, typename T = typename std::decay<Type>::type> template <typename Type, typename T = typename std::decay<Type>::type>
using EnableIfTuple = void_t< using EnableIfTuple = void_t<
decltype(Get<0, T>::impl(std::declval<T>())), decltype(get_impl<0>(std::declval<T>())),
decltype(std::tuple_size<T>::value)>; decltype(std::tuple_size<T>::value)>;
template <typename, typename T>
struct IsTuple : std::false_type {};
template <typename T>
struct IsTuple<EnableIfTuple<T>, T> : std::true_type {};
/** /**
* Check if the sequence is a range * Check if the sequence is a range
...@@ -110,179 +113,124 @@ template <typename Type, typename T = typename std::decay<Type>::type> ...@@ -110,179 +113,124 @@ template <typename Type, typename T = typename std::decay<Type>::type>
using EnableIfRange = void_t< using EnableIfRange = void_t<
decltype(adl::adl_begin(std::declval<T>())), decltype(adl::adl_begin(std::declval<T>())),
decltype(adl::adl_end(std::declval<T>()))>; decltype(adl::adl_end(std::declval<T>()))>;
template <typename, typename T>
struct IsRange : std::false_type {};
template <typename T>
struct IsRange<EnableIfRange<T>, T> : std::true_type {};
/** struct TupleTag {};
* Forwards the return value of the first element of the sequence, used to struct RangeTag {};
* determine the type of the first element in the range in SFINAE use cases
*/
template <typename Sequence, typename = void>
struct DeclvalSequence {
using type = decltype(*(adl::adl_begin(std::declval<Sequence>())));
};
template <typename Sequence>
struct DeclvalSequence<Sequence, EnableIfTuple<Sequence>> {
using type = decltype(Get<0, Sequence>::impl(std::declval<Sequence>()));
};
/** /**
* Check if the functor accepts one or two arguments, one of the first element * Should ideally check if it is a tuple and if not return void, but msvc fails
* in the sequence, assuming that all the other elements can also be passed to
* the functor, and the second being an instantiation of std::integral_constant,
* and the third being an instantiation of LoopControl, to provide breakability
* to the loop
*/
template <typename Sequence, typename Func>
using EnableIfAcceptsOneArgument = void_t<decltype(std::declval<Func>()(
std::declval<typename DeclvalSequence<Sequence>::type>()))>;
template <typename Sequence, typename Func>
using EnableIfAcceptsTwoArguments = void_t<decltype(std::declval<Func>()(
std::declval<typename DeclvalSequence<Sequence>::type>(),
index_constant<0>{}))>;
template <typename Sequence, typename Func>
using EnableIfAcceptsThreeArguments = void_t<decltype(std::declval<Func>()(
std::declval<typename DeclvalSequence<Sequence>::type>(),
index_constant<0>{},
adl::adl_begin(std::declval<Sequence>())))>;
template <typename Sequence, typename Func>
using EnableIfBreaksRange = std::enable_if_t<std::is_same<
typename std::decay<decltype(std::declval<Func>()(
std::declval<typename DeclvalSequence<Sequence>::type>(),
std::size_t{0},
adl::adl_begin(std::declval<Sequence>())))>::type,
LoopControl>::value>;
template <typename Sequence, typename Func>
using EnableIfBreaksTuple = std::enable_if_t<std::is_same<
typename std::decay<decltype(std::declval<Func>()(
std::declval<typename DeclvalSequence<Sequence>::type>(),
index_constant<0>{}))>::type,
LoopControl>::value>;
/**
* Enables if the sequence has random access iterators
*/ */
template <typename Sequence> template <typename Sequence>
using EnableIfRandomAccessIterators = std::enable_if_t<std::is_same< using SequenceTag =
typename std::iterator_traits<typename std::decay<decltype( std::conditional_t<IsRange<void, Sequence>::value, RangeTag, TupleTag>;
adl::adl_begin(std::declval<Sequence>()))>::type>::iterator_category,
std::random_access_iterator_tag>::value>; struct BeginAddTag {};
template <typename Sequence, typename Index> struct IndexingTag {};
using EnableIfHasIndexingOperator =
void_t<decltype(std::declval<Sequence>()[std::declval<Index>()])>; template <typename Func, typename Item, typename Iter>
using ForEachImplTag = std::conditional_t<
is_invocable<Func, Item, index_constant<0>, Iter>::value,
index_constant<3>,
std::conditional_t<
is_invocable<Func, Item, index_constant<0>>::value,
index_constant<2>,
std::conditional_t<
is_invocable<Func, Item>::value,
index_constant<1>,
void>>>;
template <
typename Func,
typename... Args,
std::enable_if_t<is_invocable_r<LoopControl, Func, Args...>::value, int> =
0>
LoopControl invoke_returning_loop_control(Func&& f, Args&&... a) {
return static_cast<Func&&>(f)(static_cast<Args&&>(a)...);
}
template <
typename Func,
typename... Args,
std::enable_if_t<!is_invocable_r<LoopControl, Func, Args...>::value, int> =
0>
LoopControl invoke_returning_loop_control(Func&& f, Args&&... a) {
static_assert(
std::is_void<invoke_result_t<Func, Args...>>::value,
"return either LoopControl or void");
return static_cast<Func&&>(f)(static_cast<Args&&>(a)...), loop_continue;
}
/** /**
* Implementation for the range iteration, this provides specializations in * Implementations for the runtime function
* the case where the function returns a break or continue
*/ */
template <typename Seq, typename F, typename = void> template <typename Sequence, typename Func>
struct ForEachRange { void for_each_range_impl(index_constant<3>, Sequence&& range, Func& func) {
template <typename Sequence, typename Func>
static void impl(Sequence&& range, Func& func) {
auto first = adl::adl_begin(range);
auto last = adl::adl_end(range);
for (auto index = std::size_t{0}; first != last; ++index) {
auto next = std::next(first);
func(*first, index, first);
first = next;
}
}
};
template <typename Seq, typename F>
struct ForEachRange<Seq, F, EnableIfBreaksRange<Seq, F>> {
template <typename Sequence, typename Func>
static void impl(Sequence&& range, Func& func) {
auto first = adl::adl_begin(range); auto first = adl::adl_begin(range);
auto last = adl::adl_end(range); auto last = adl::adl_end(range);
for (auto index = std::size_t{0}; first != last; ++index) { for (auto index = std::size_t{0}; first != last; ++index) {
auto next = std::next(first); auto next = std::next(first);
if (loop_break == func(*first, index, first)) { auto control = invoke_returning_loop_control(func, *first, index, first);
if (loop_break == control) {
break; break;
} }
first = next; first = next;
} }
}
};
/**
* Implementations for the runtime function
*/
template <
typename Sequence,
typename Func,
EnableIfAcceptsThreeArguments<Sequence, Func>* = nullptr>
void for_each_range_impl(Sequence&& range, Func& func) {
ForEachRange<Sequence, Func>::impl(std::forward<Sequence>(range), func);
} }
template < template <typename Sequence, typename Func>
typename Sequence, void for_each_range_impl(index_constant<2>, Sequence&& range, Func& func) {
typename Func,
EnableIfAcceptsTwoArguments<Sequence, Func>* = nullptr>
void for_each_range_impl(Sequence&& range, Func& func) {
// make a three arg adaptor for the function passed in so that the main // make a three arg adaptor for the function passed in so that the main
// implementation function can be used // implementation function can be used
auto three_arg_adaptor = [&func]( auto three_arg_adaptor = [&func](
auto&& ele, auto index, auto) -> decltype(auto) { auto&& ele, auto index, auto) -> decltype(auto) {
return func(std::forward<decltype(ele)>(ele), index); return func(std::forward<decltype(ele)>(ele), index);
}; };
for_each_range_impl(std::forward<Sequence>(range), three_arg_adaptor); for_each_range_impl(
index_constant<3>{}, std::forward<Sequence>(range), three_arg_adaptor);
} }
template < template <typename Sequence, typename Func>
typename Sequence, void for_each_range_impl(index_constant<1>, Sequence&& range, Func& func) {
typename Func,
EnableIfAcceptsOneArgument<Sequence, Func>* = nullptr>
void for_each_range_impl(Sequence&& range, Func& func) {
// make a three argument adaptor for the function passed in that just ignores // make a three argument adaptor for the function passed in that just ignores
// the second and third argument // the second and third argument
auto three_arg_adaptor = [&func](auto&& ele, auto, auto) -> decltype(auto) { auto three_arg_adaptor = [&func](auto&& ele, auto, auto) -> decltype(auto) {
return func(std::forward<decltype(ele)>(ele)); return func(std::forward<decltype(ele)>(ele));
}; };
for_each_range_impl(std::forward<Sequence>(range), three_arg_adaptor); for_each_range_impl(
index_constant<3>{}, std::forward<Sequence>(range), three_arg_adaptor);
} }
/** /**
* Handlers for iteration * Handlers for iteration
*/ */
/** template <typename Sequence, typename Func, std::size_t... Indices>
* The class provides a way to tell whether the function passed in to the void for_each_tuple_impl(
* algorithm returns an instance of LoopControl, if it does then the break-able index_sequence<Indices...>,
* implementation will be used. If the function provided to the algorithm Sequence&& seq,
* does not use the break API, then the basic no break, 0 overhead Func& func) {
* implementation will be used using _ = int[];
*/
template <typename Seq, typename F, typename = void>
struct ForEachTupleImpl {
template <typename Sequence, typename Func, std::size_t... Indices>
static void impl(Sequence&& seq, Func& func, index_sequence<Indices...>) {
// unroll the loop in an initializer list construction parameter expansion
// pack
static_cast<void>(std::initializer_list<int>{
(func(
Get<Indices, Sequence>::impl(std::forward<Sequence>(seq)),
index_constant<Indices>{}),
0)...});
}
};
template <typename Seq, typename F>
struct ForEachTupleImpl<Seq, F, EnableIfBreaksTuple<Seq, F>> {
template <typename Sequence, typename Func, std::size_t... Indices>
static void impl(Sequence&& seq, Func& func, index_sequence<Indices...>) {
// unroll the loop in an initializer list construction parameter expansion // unroll the loop in an initializer list construction parameter expansion
// pack // pack
LoopControl break_or_not = LoopControl::CONTINUE; auto control = loop_continue;
// cast to void to ignore the result, use the initialzer list constructor // cast to void to ignore the result; use the int[] initialization to do the
// to do the loop execution, the ternary conditional will decide whether // loop execution, the ternary conditional will decide whether or not to
// or not to evaluate the result // evaluate the result
static_cast<void>(std::initializer_list<int>{ //
(((break_or_not == loop_continue) // if func does not return loop-control, expect the optimizer to see through
? (break_or_not = func( // invoke_returning_loop_control always returning loop_continue
Get<Indices, Sequence>::impl(std::forward<Sequence>(seq)), void(
_{(((control == loop_continue)
? (control = invoke_returning_loop_control(
func,
get_impl<Indices>(std::forward<Sequence>(seq)),
index_constant<Indices>{})) index_constant<Indices>{}))
: (loop_continue)), : (loop_continue)),
0)...}); 0)...});
} }
};
/** /**
* The two top level compile time loop iteration functions handle the dispatch * The two top level compile time loop iteration functions handle the dispatch
...@@ -292,97 +240,90 @@ struct ForEachTupleImpl<Seq, F, EnableIfBreaksTuple<Seq, F>> { ...@@ -292,97 +240,90 @@ struct ForEachTupleImpl<Seq, F, EnableIfBreaksTuple<Seq, F>> {
* which is passed on to the 2 argument specialization, which then in turn * which is passed on to the 2 argument specialization, which then in turn
* forwards implementation to the implementation classes above * forwards implementation to the implementation classes above
*/ */
template < template <typename Sequence, typename Func>
typename Sequence, void for_each_tuple_impl(index_constant<2>, Sequence&& seq, Func& func) {
typename Func,
EnableIfAcceptsTwoArguments<Sequence, Func>* = nullptr>
void for_each_tuple_impl(Sequence&& seq, Func& func) {
// pass the length as an index sequence to the implementation as an // pass the length as an index sequence to the implementation as an
// optimization over manual template "tail recursion" unrolling // optimization over manual template "tail recursion" unrolling
constexpr auto length = using size = std::tuple_size<typename std::decay<Sequence>::type>;
std::tuple_size<typename std::decay<Sequence>::type>::value; for_each_tuple_impl(
ForEachTupleImpl<Sequence, Func>::impl( make_index_sequence<size::value>{}, std::forward<Sequence>(seq), func);
std::forward<Sequence>(seq), func, make_index_sequence<length>{});
} }
template < template <typename Sequence, typename Func>
typename Sequence, void for_each_tuple_impl(index_constant<1>, Sequence&& seq, Func& func) {
typename Func,
EnableIfAcceptsOneArgument<Sequence, Func>* = nullptr>
void for_each_tuple_impl(Sequence&& seq, Func& func) {
// make an adaptor for the function passed in, in case it can only be passed // make an adaptor for the function passed in, in case it can only be passed
// on argument // on argument
auto two_arg_adaptor = [&func](auto&& ele, auto) -> decltype(auto) { auto two_arg_adaptor = [&func](auto&& ele, auto) -> decltype(auto) {
return func(std::forward<decltype(ele)>(ele)); return func(std::forward<decltype(ele)>(ele));
}; };
for_each_tuple_impl(std::forward<Sequence>(seq), two_arg_adaptor); for_each_tuple_impl(
index_constant<2>{}, std::forward<Sequence>(seq), two_arg_adaptor);
} }
/** /**
* Top level handlers for the for_each loop, the basic specialization handles * Top level handlers for the for_each loop, with one overload for tuples and
* tuples and the specialized version handles ranges * one overload for ranges
* *
* This implies that if type is both a range and a tuple, it is treated as a * This implies that if type is both a range and a tuple, it is treated as a
* range rather than as a tuple * range rather than as a tuple
*/ */
template <typename R, typename = void> template <typename Sequence, typename Func>
struct ForEachImpl { static void for_each_impl(TupleTag, Sequence&& range, Func& func) {
template <typename Sequence, typename Func> using type = decltype(get_impl<0>(std::declval<Sequence>()));
static void impl(Sequence&& range, Func& func) { using tag = ForEachImplTag<Func, type, void>;
for_each_tuple_impl(std::forward<Sequence>(range), func); static_assert(!std::is_same<tag, void>::value, "unknown invocability");
} for_each_tuple_impl(tag{}, std::forward<Sequence>(range), func);
}; }
template <typename R> template <typename Sequence, typename Func>
struct ForEachImpl<R, EnableIfRange<R>> { static void for_each_impl(RangeTag, Sequence&& range, Func& func) {
template <typename Sequence, typename Func> using iter = decltype(adl::adl_begin(std::declval<Sequence>()));
static void impl(Sequence&& range, Func& func) { using type = decltype(*std::declval<iter>());
for_each_range_impl(std::forward<Sequence>(range), func); using tag = ForEachImplTag<Func, type, iter>;
} static_assert(!std::is_same<tag, void>::value, "unknown invocability");
}; for_each_range_impl(tag{}, std::forward<Sequence>(range), func);
}
template <typename S, typename I, typename = void> template <typename Sequence, typename Index>
struct FetchIteratorIndexImpl { decltype(auto) fetch_impl(IndexingTag, Sequence&& sequence, Index&& index) {
template <typename Sequence, typename Index>
static decltype(auto) impl(Sequence&& sequence, Index&& index) {
return std::forward<Sequence>(sequence)[std::forward<Index>(index)]; return std::forward<Sequence>(sequence)[std::forward<Index>(index)];
} }
}; template <typename Sequence, typename Index>
template <typename S, typename I> decltype(auto) fetch_impl(BeginAddTag, Sequence&& sequence, Index index) {
struct FetchIteratorIndexImpl<S, I, EnableIfRandomAccessIterators<S>> {
template <typename Sequence, typename Index>
static decltype(auto) impl(Sequence&& sequence, Index index) {
return *(adl::adl_begin(std::forward<Sequence>(sequence)) + index); return *(adl::adl_begin(std::forward<Sequence>(sequence)) + index);
} }
};
template <typename S, typename = void> template <typename Sequence, typename Index>
struct FetchImpl { decltype(auto) fetch_impl(TupleTag, Sequence&& sequence, Index index) {
template <typename Sequence, typename Index> return get_impl<index>(std::forward<Sequence>(sequence));
static decltype(auto) impl(Sequence&& sequence, Index index) { }
return Get<static_cast<std::size_t>(index), Sequence>::impl( template <typename Sequence, typename Index>
std::forward<Sequence>(sequence)); decltype(auto) fetch_impl(RangeTag, Sequence&& sequence, Index&& index) {
} using iter = decltype(adl::adl_begin(std::declval<Sequence>()));
}; using iter_traits = std::iterator_traits<remove_cvref_t<iter>>;
template <typename S> using iter_cat = typename iter_traits::iterator_category;
struct FetchImpl<S, EnableIfRange<S>> { using tag = std::conditional_t<
template <typename Sequence, typename Index> std::is_same<iter_cat, std::random_access_iterator_tag>::value,
static decltype(auto) impl(Sequence&& sequence, Index&& index) { BeginAddTag,
return FetchIteratorIndexImpl<Sequence, Index>::impl( IndexingTag>;
std::forward<Sequence>(sequence), std::forward<Index>(index)); return fetch_impl(
} tag{}, std::forward<Sequence>(sequence), std::forward<Index>(index));
}; }
} // namespace for_each_detail } // namespace for_each_detail
template <typename Sequence, typename Func> template <typename Sequence, typename Func>
FOLLY_CPP14_CONSTEXPR Func for_each(Sequence&& range, Func func) { FOLLY_CPP14_CONSTEXPR Func for_each(Sequence&& sequence, Func func) {
for_each_detail::ForEachImpl<typename std::decay<Sequence>::type>::impl( namespace fed = for_each_detail;
std::forward<Sequence>(range), func); using tag = fed::SequenceTag<Sequence>;
fed::for_each_impl(tag{}, std::forward<Sequence>(sequence), func);
return func; return func;
} }
template <typename Sequence, typename Index> template <typename Sequence, typename Index>
FOLLY_CPP14_CONSTEXPR decltype(auto) fetch(Sequence&& sequence, Index&& index) { FOLLY_CPP14_CONSTEXPR decltype(auto) fetch(Sequence&& sequence, Index&& index) {
return for_each_detail::FetchImpl<Sequence>::impl( namespace fed = for_each_detail;
std::forward<Sequence>(sequence), std::forward<Index>(index)); using tag = fed::SequenceTag<Sequence>;
return for_each_detail::fetch_impl(
tag{}, std::forward<Sequence>(sequence), std::forward<Index>(index));
} }
} // namespace folly } // namespace folly
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