Commit 05db64e6 authored by Aaryaman Sagar's avatar Aaryaman Sagar Committed by Facebook Github Bot

Added a for_each function to iterate through ranges

Summary:
Adding a for_each function that allows generalized indexed and breakable iteration through ranges, these can either be runtime ranges (i.e. entities for which std::begin and std::end work) or compile time ranges (as deemed by the presence of a std::tuple_length<>, get<> (ADL resolved) functions)

The function is made to provide a convenient library based solution to the proposal p0589r0, which aims to generalize the range based for loop even further to work with compile time ranges

A drawback of using range based for loops is that sometimes you do not have access to the index within the range.  This provides easy access to that, even with compile time ranges.

Further this also provides a good way to break out of a loop without any overhead when that is not used.

A simple use case would be when using futures, if the user was doing calls to n servers then they would accept the callback with the futures like this

   auto vec = std::vector<std::future<int>>{request_one(), ...};
   when_all(vec.begin(), vec.end()).then([](auto futures) {
     folly::for_each(futures, [](auto& fut) { ... });
   });

Now when this code switches to use tuples instead of the runtime std::vector, then the loop does not need to change, the code will still work just fine

   when_all(future_one, future_two, future_three).then([](auto futures) {
     folly::for_each(futures, [](auto& fut) { ... });
   });

Reviewed By: yfeldblum

Differential Revision: D5557336

fbshipit-source-id: 79fcbafa7e1671f8856f0dcb7bf7996435dadeaa
parent 02fe20e3
This diff is collapsed.
......@@ -16,20 +16,113 @@
#pragma once
#include <folly/Preprocessor.h>
#include <type_traits>
namespace folly {
/**
* This header is deprecated
* @function for_each
*
* folly::for_each is a generalized iteration algorithm. Example:
*
* auto one = std::make_tuple(1, 2, 3);
* auto two = std::vector<int>{1, 2, 3};
* auto func = [](auto element, auto index) {
* cout << index << " : " << element << endl;
* };
* folly::for_each(one, func);
* folly::for_each(two, func);
*
* The for_each function allows iteration through sequences, these
* can either be runtime sequences (i.e. entities for which std::begin and
* std::end work) or compile time sequences (as deemed by the presence of
* std::tuple_length<>, get<> (ADL resolved) functions)
*
* Use range-based for loops, and if necessary Ranges-v3.
* The function is made to provide a convenient library based alternative to
* the proposal p0589r0, which aims to generalize the range based for loop
* even further to work with compile time sequences.
*
* Some of the messaging here presumes that you have coded:
* A drawback of using range based for loops is that sometimes you do not have
* access to the index within the range. This provides easy access to that,
* even with compile time sequences.
*
* #include <range/v3/all.hpp>
* using namespace ranges;
* And breaking out is easy
*
* auto range_one = std::vector<int>{1, 2, 3};
* auto range_two = std::make_tuple(1, 2, 3);
* auto func = [](auto ele, auto index) {
* cout << "Element at index " << index << " : " << ele;
* if (index == 1) {
* return folly::loop_break;
* }
* return folly::loop_continue;
* };
* folly_for_each(range_one, func);
* folly_for_each(range_two, func);
*
* A simple use case would be when using futures, if the user was doing calls
* to n servers then they would accept the callback with the futures like this
*
* auto vec = std::vector<std::future<int>>{request_one(), ...};
* when_all(vec.begin(), vec.end()).then([](auto futures) {
* folly::for_each(futures, [](auto& fut) { ... });
* });
*
* Now when this code switches to use tuples instead of the runtime
* std::vector, then the loop does not need to change, the code will still
* work just fine
*
* when_all(future_one, future_two, future_three).then([](auto futures) {
* folly::for_each(futures, [](auto& fut) { ... });
* });
*/
template <typename Range, typename Func>
constexpr Func for_each(Range&& range, Func func);
#include <folly/Preprocessor.h>
#include <type_traits>
/**
* The user should return loop_break and loop_continue if they want to iterate
* in such a way that they can preemptively stop the loop and break out when
* certain conditions are met
*/
namespace for_each_detail {
enum class LoopControl : bool { BREAK, CONTINUE };
} // namespace for_each_detail
constexpr auto loop_break = for_each_detail::LoopControl::BREAK;
constexpr auto loop_continue = for_each_detail::LoopControl::CONTINUE;
/**
* Utility method to help access elements of a sequence with one uniform
* interface
*
* This can be useful for example when you are looping through a sequence and
* want to modify another sequence based on the information in the current
* sequence
*
* auto range_one = std::make_tuple(1, 2, 3);
* auto range_two = std::make_tuple(4, 5, 6);
* folly::for_each(range_one, [&range_two](auto ele, auto index) {
* folly::fetch(range_two, index) = ele;
* });
*
* For non-tuple like ranges, this works by first trying to use the iterator
* class if the iterator has been marked to be a random access iterator. This
* should be inspectable via the std::iterator_traits traits class. If the
* iterator class is not present or is not a random access iterator then the
* implementation falls back to trying to use the indexing operator
* (operator[]) to fetch the required element
*/
template <typename Sequence, typename Index>
constexpr decltype(auto) fetch(Sequence&& sequence, Index&& index);
} // namespace folly
/**
* Everything below this is deprecated. Use the folly::for_each algorithm above
* instead
*/
/*
* Form a local variable name from "FOR_EACH_" x __LINE__, so that
* FOR_EACH can be nested without creating shadowed declarations.
......@@ -223,3 +316,5 @@ downTo(T& iter, const U& begin) {
*/
#define FOR_EACH_RANGE_R(i, begin, end) \
for (auto i = (false ? (begin) : (end)); ::folly::detail::downTo(i, (begin));)
#include <folly/Foreach-inl.h>
......@@ -187,6 +187,7 @@ nobase_follyinclude_HEADERS = \
FixedString.h \
folly-config.h \
Foreach.h \
Foreach-inl.h \
FormatArg.h \
FormatTraits.h \
Format.h \
......
......@@ -32,47 +32,259 @@ using namespace folly::detail;
// 3. Use FOR_EACH_KV loop to iterate through the map.
std::map<int, std::string> bmMap; // For use in benchmarks below.
std::vector<int> vec_one;
std::vector<int> vec_two;
void setupBenchmark(size_t iters) {
bmMap.clear();
for (size_t i = 0; i < iters; ++i) {
bmMap[i] = "teststring";
}
vec_one.clear();
vec_two.clear();
vec_one.resize(iters);
vec_two.resize(iters);
}
BENCHMARK(ForEachKVNoMacroAssign, iters) {
BENCHMARK(ForEachFunctionNoAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
BENCHMARK_SUSPEND { setupBenchmark(iters); }
suspender.dismissing([&]() {
folly::for_each(bmMap, [&](auto& key_val_pair) {
sumKeys += key_val_pair.first;
sumValues += key_val_pair.second;
});
doNotOptimizeAway(sumKeys);
});
}
FOR_EACH(iter, bmMap) {
BENCHMARK(StdForEachFunctionNoAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
std::for_each(bmMap.begin(), bmMap.end(), [&](auto& key_val_pair) {
sumKeys += key_val_pair.first;
sumValues += key_val_pair.second;
});
doNotOptimizeAway(sumKeys);
});
}
BENCHMARK(RangeBasedForLoopNoAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
for (auto& key_val_pair : bmMap) {
sumKeys += key_val_pair.first;
sumValues += key_val_pair.second;
}
doNotOptimizeAway(sumKeys);
});
}
BENCHMARK(ManualLoopNoAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
for (auto iter = bmMap.begin(); iter != bmMap.end(); ++iter) {
sumKeys += iter->first;
sumValues += iter->second;
}
doNotOptimizeAway(sumKeys);
});
}
BENCHMARK(ForEachFunctionAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
folly::for_each(bmMap, [&](auto& key_val_pair) {
const int k = key_val_pair.first;
const std::string v = key_val_pair.second;
sumKeys += k;
sumValues += v;
});
});
}
BENCHMARK(StdForEachFunctionAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
std::for_each(bmMap.begin(), bmMap.end(), [&](auto& key_val_pair) {
const int k = key_val_pair.first;
const std::string v = key_val_pair.second;
sumKeys += k;
sumValues += v;
});
});
}
BENCHMARK(RangeBasedForLoopAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
for (auto& key_val_pair : bmMap) {
const int k = key_val_pair.first;
const std::string v = key_val_pair.second;
sumKeys += k;
sumValues += v;
}
});
}
BENCHMARK(ManualLoopAssign, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
for (auto iter = bmMap.begin(); iter != bmMap.end(); ++iter) {
const int k = iter->first;
const std::string v = iter->second;
sumKeys += k;
sumValues += v;
}
});
}
BENCHMARK(ForEachKVNoMacroNoAssign, iters) {
BENCHMARK(ForEachFunctionNoAssignWithIndexManipulation, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
folly::for_each(bmMap, [&](auto& key_val_pair, auto index) {
sumKeys += key_val_pair.first;
sumValues += key_val_pair.second;
sumValues += index;
});
});
}
BENCHMARK(StdForEachFunctionNoAssignWithIndexManipulation, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
auto index = std::size_t{0};
std::for_each(bmMap.begin(), bmMap.end(), [&](auto& key_val_pair) {
sumKeys += key_val_pair.first;
sumValues += key_val_pair.second;
sumValues += index;
++index;
});
});
}
BENCHMARK(RangeBasedForLoopNoAssignWithIndexManipulation, iters) {
BenchmarkSuspender suspender;
int sumKeys = 0;
std::string sumValues;
setupBenchmark(iters);
suspender.dismissing([&]() {
auto index = std::size_t{0};
for (auto& key_val_pair : bmMap) {
sumKeys += key_val_pair.first;
sumValues += key_val_pair.second;
sumValues += index;
}
});
}
BENCHMARK(ForEachFunctionFetch, iters) {
BenchmarkSuspender suspender;
setupBenchmark(iters);
suspender.dismissing([&]() {
folly::for_each(bmMap, [&](auto& key_val_pair, auto index) {
folly::fetch(vec_one, index) = key_val_pair.first;
});
});
}
BENCHMARK(StdForEachFunctionFetch, iters) {
BenchmarkSuspender suspender;
setupBenchmark(iters);
suspender.dismissing([&]() {
auto index = std::size_t{0};
std::for_each(bmMap.begin(), bmMap.end(), [&](auto& key_val_pair) {
*(vec_one.begin() + index++) = key_val_pair.first;
});
});
}
BENCHMARK(ForLoopFetch, iters) {
BenchmarkSuspender suspender;
setupBenchmark(iters);
suspender.dismissing([&]() {
auto index = std::size_t{0};
for (auto& key_val_pair : bmMap) {
*(vec_one.begin() + index++) = key_val_pair.first;
}
});
}
BENCHMARK(ForEachKVNoMacroAssign, iters) {
int sumKeys = 0;
std::string sumValues;
BENCHMARK_SUSPEND { setupBenchmark(iters); }
FOR_EACH(iter, bmMap) {
sumKeys += iter->first;
sumValues += iter->second;
const int k = iter->first;
const std::string v = iter->second;
sumKeys += k;
sumValues += v;
}
}
BENCHMARK(ManualLoopNoAssign, iters) {
BENCHMARK(ForEachKVNoMacroNoAssign, iters) {
int sumKeys = 0;
std::string sumValues;
BENCHMARK_SUSPEND { setupBenchmark(iters); }
for (auto iter = bmMap.begin(); iter != bmMap.end(); ++iter) {
FOR_EACH(iter, bmMap) {
sumKeys += iter->first;
sumValues += iter->second;
}
......
......@@ -16,9 +16,12 @@
#include <folly/Foreach.h>
#include <initializer_list>
#include <iterator>
#include <list>
#include <map>
#include <string>
#include <tuple>
#include <vector>
#include <folly/portability/GTest.h>
......@@ -26,6 +29,296 @@
using namespace folly;
using namespace folly::detail;
namespace folly {
namespace test {
class TestRValueConstruct {
public:
TestRValueConstruct() = default;
TestRValueConstruct(TestRValueConstruct&&) noexcept {
this->constructed_from_rvalue = true;
}
TestRValueConstruct(const TestRValueConstruct&) {
this->constructed_from_rvalue = false;
}
TestRValueConstruct& operator=(const TestRValueConstruct&) = delete;
TestRValueConstruct& operator=(TestRValueConstruct&&) = delete;
bool constructed_from_rvalue{false};
};
class TestAdlIterable {
public:
std::vector<int> vec{0, 1, 2, 3};
};
auto begin(TestAdlIterable& instance) {
return instance.vec.begin();
}
auto begin(const TestAdlIterable& instance) {
return instance.vec.begin();
}
auto end(TestAdlIterable& instance) {
return instance.vec.end();
}
auto end(const TestAdlIterable& instance) {
return instance.vec.end();
}
class TestBothIndexingAndIter {
public:
class Iterator {
public:
using difference_type = std::size_t;
using value_type = int;
using pointer = int*;
using reference = int&;
using iterator_category = std::random_access_iterator_tag;
int& operator*() {
return this->val;
}
Iterator operator+(int) {
return *this;
}
explicit Iterator(int& val_in) : val{val_in} {}
int& val;
};
auto begin() {
this->called_begin = true;
return Iterator{val};
}
auto end() {
return Iterator{val};
}
int& operator[](int) {
return this->val;
}
int val{0};
bool called_begin = false;
};
} // namespace test
} // namespace folly
TEST(Foreach, ForEachFunctionBasic) {
auto range = std::make_tuple(1, 2, 3);
auto result_range = std::vector<int>{};
auto correct_result_range = std::vector<int>{1, 2, 3};
folly::for_each(range, [&](auto ele) { result_range.push_back(ele); });
EXPECT_TRUE(std::equal(
result_range.begin(), result_range.end(), correct_result_range.begin()));
}
TEST(Foreach, ForEachFunctionBasicRuntimeOneArg) {
auto range = std::vector<int>{1, 2, 3};
auto current = 0;
folly::for_each(range, [&](auto ele) {
if (current == 0) {
EXPECT_EQ(ele, 1);
} else if (current == 1) {
EXPECT_EQ(ele, 2);
} else {
EXPECT_EQ(ele, 3);
}
++current;
});
}
TEST(Foreach, ForEachFunctionBasicRuntimeTwoArg) {
auto range = std::vector<int>{1, 2, 3};
folly::for_each(range, [](auto ele, auto index) {
EXPECT_TRUE(index < 3);
if (index == 0) {
EXPECT_EQ(ele, 1);
} else if (index == 1) {
EXPECT_EQ(ele, 2);
} else if (index == 2) {
EXPECT_EQ(ele, 3);
}
});
}
TEST(Foreach, ForEachFunctionBasicRuntimeThreeArg) {
auto range = std::list<int>{1, 2, 3};
auto result_range = std::list<int>{1, 3};
folly::for_each(range, [&](auto ele, auto, auto iter) {
if (ele == 2) {
range.erase(iter);
}
});
EXPECT_TRUE(std::equal(range.begin(), range.end(), result_range.begin()));
}
TEST(Foreach, ForEachFunctionBasicTupleOneArg) {
auto range = std::make_tuple(1, 2, 3);
auto current = 0;
folly::for_each(range, [&](auto ele) {
if (current == 0) {
EXPECT_EQ(ele, 1);
} else if (current == 1) {
EXPECT_EQ(ele, 2);
} else {
EXPECT_EQ(ele, 3);
}
++current;
});
}
TEST(Foreach, ForEachFunctionBasicTupleTwoArg) {
auto range = std::make_tuple(1, 2, 3);
folly::for_each(range, [](auto ele, auto index) {
EXPECT_TRUE(index < 3);
if (index == 0) {
EXPECT_EQ(ele, 1);
} else if (index == 1) {
EXPECT_EQ(ele, 2);
} else if (index == 2) {
EXPECT_EQ(ele, 3);
}
});
}
TEST(Foreach, ForEachFunctionBreakRuntimeOneArg) {
auto range = std::vector<int>{1, 2, 3};
auto iterations = 0;
folly::for_each(range, [&](auto) {
++iterations;
if (iterations == 1) {
return folly::loop_break;
}
return folly::loop_continue;
});
EXPECT_EQ(iterations, 1);
}
TEST(Foreach, ForEachFunctionBreakRuntimeTwoArg) {
auto range = std::vector<int>{1, 2, 3};
auto iterations = 0;
folly::for_each(range, [&](auto, auto index) {
++iterations;
if (index == 1) {
return folly::loop_break;
}
return folly::loop_continue;
});
EXPECT_EQ(iterations, 2);
}
TEST(Foreach, ForEachFunctionBreakRuntimeThreeArg) {
auto range = std::vector<int>{1, 2, 3};
auto iterations = 0;
folly::for_each(range, [&](auto, auto index, auto) {
++iterations;
if (index == 1) {
return folly::loop_break;
}
return folly::loop_continue;
});
EXPECT_EQ(iterations, 2);
}
TEST(Foreach, ForEachFunctionBreakTupleOneArg) {
auto range = std::vector<int>{1, 2, 3};
auto iterations = 0;
folly::for_each(range, [&](auto) {
++iterations;
if (iterations == 1) {
return folly::loop_break;
}
return folly::loop_continue;
});
EXPECT_EQ(iterations, 1);
}
TEST(Foreach, ForEachFunctionBreakTupleTwoArg) {
auto range = std::vector<int>{1, 2, 3};
auto iterations = 0;
folly::for_each(range, [&](auto, auto index) {
++iterations;
if (index == 1) {
return folly::loop_break;
}
return folly::loop_continue;
});
EXPECT_EQ(iterations, 2);
}
TEST(Foreach, ForEachFunctionArray) {
auto range = std::array<int, 3>{{1, 2, 3}};
auto iterations = 0;
folly::for_each(range, [&](auto, auto index) {
++iterations;
if (index == 1) {
return folly::loop_break;
}
return folly::loop_continue;
});
EXPECT_EQ(iterations, 2);
}
TEST(Foreach, ForEachFunctionInitializerListBasic) {
folly::for_each(std::initializer_list<int>{1, 2, 3}, [](auto ele) { ++ele; });
}
TEST(Foreach, ForEachFunctionTestForward) {
using folly::test::TestRValueConstruct;
auto range_one = std::vector<TestRValueConstruct>{};
range_one.resize(3);
folly::for_each(std::move(range_one), [](auto ele) {
EXPECT_FALSE(ele.constructed_from_rvalue);
});
folly::for_each(
std::make_tuple(TestRValueConstruct{}, TestRValueConstruct{}),
[](auto ele) { EXPECT_TRUE(ele.constructed_from_rvalue); });
}
TEST(Foreach, ForEachFunctionAdlIterable) {
auto range = test::TestAdlIterable{};
auto iterations = 0;
folly::for_each(range, [&](auto ele, auto index) {
++iterations;
EXPECT_EQ(ele, index);
});
EXPECT_EQ(iterations, 4);
}
TEST(ForEach, FetchRandomAccessIterator) {
auto vec = std::vector<int>{1, 2, 3};
auto& second = folly::fetch(vec, 1);
EXPECT_EQ(second, 2);
second = 3;
EXPECT_EQ(second, 3);
}
TEST(ForEach, FetchIndexing) {
auto mp = std::map<int, int>{{1, 2}};
auto& ele = folly::fetch(mp, 1);
EXPECT_EQ(ele, 2);
ele = 3;
EXPECT_EQ(ele, 3);
}
TEST(ForEach, FetchTuple) {
auto mp = std::make_tuple(1, 2, 3);
auto& ele = folly::fetch(mp, std::integral_constant<int, 1>{});
EXPECT_EQ(ele, 2);
ele = 3;
EXPECT_EQ(ele, 3);
}
TEST(ForEach, FetchTestPreferIterator) {
auto range = test::TestBothIndexingAndIter{};
auto& ele = folly::fetch(range, 0);
EXPECT_TRUE(range.called_begin);
EXPECT_EQ(ele, 0);
ele = 2;
EXPECT_EQ(folly::fetch(range, 0), 2);
}
TEST(Foreach, ForEachRvalue) {
const char* const hello = "hello";
int n = 0;
......
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