Here I present a first cut of two variants of the template function over(vec, f).
Both versions iterate over a vector-like object and call a function object for each element.
One version calls the function object with two arguments - an element reference and an index - the second with just the element reference.
The idea is to get the compiler to select the version that matches the passed-in lambda, so the user can express intent in the lambda signature without having to select a differently-named free function.
here's the code:
#include <vector>
#include <iostream>
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template<class Vector, class F>
auto over(Vector &&vec, F &&f)
-> void_t<decltype(f(vec.operator[](std::declval<std::size_t>()), std::declval<std::size_t>()))>
{
const auto size = vec.size();
for (std::size_t i = 0; i < size; ++i) {
f(vec[i], i);
}
}
template<class Vector, class F>
auto over(Vector &&vec, F &&f)
-> void_t<decltype(f(*vec.begin()))>
{
for (auto &&x : vec) {
f(x);
}
}
int main() {
std::vector<float> vf = {1.0, 1.1, 1.2};
std::cout << "two-argument form:\n";
over(vf, [](auto &&val, auto &&index) {
std::cout << index << " : " << val << std::endl;
});
std::cout << "\none-argument form:\n";
over(vf, [](auto &&val) {
std::cout << val << std::endl;
});
}
Question:
You will see that the clause inside the void_t<> return type generator knows all about the implementation of the function. I am displeased by this as:
a) it's leaking implementation details in the interface, and
b) it's not DRY.
Is there a better way to achieve this which:
a) allows the implementation to change without changing the template-enabler,
b) doesn't look like my dogs had a play-fight on my keyboard?
For this example, avoiding the "repetition" is going to be way more work/complexity than the repetition itself, but the basic idea is to count the ar-iness of the function, and then dispatch appropriately. A very similar problem is discussed here: Call function with part of variadic arguments. Using the implementation of function_traits you can can implement a function called dispatch (I called it foo in my answer to that question):
template<typename F, std::size_t... Is, class Tup>
void dispatch_impl(F && f, std::index_sequence<Is...>, Tup && tup) {
std::forward<F>(f)( std::get<Is>(std::move(tup))... );
}
template<typename F, typename... Args>
void dispatch(F && f, Args&&... args) {
dispatch_impl(std::forward<F>(f),
std::make_index_sequence<function_traits<F>::arity>{},
std::forward_as_tuple(args...) );
}
template<class Vector, class F>
void over(Vector &&vec, F &&f)
{
std::size_t i = 0;
for (auto &&x : vec) {
dispatch(std::forward<F>(f), x, i);
++i;
}
}
This answer is 14 compliant as well. Live example: http://coliru.stacked-crooked.com/a/14750cef6b735d7e.
Edit: This approach does not work with generic lambdas. So another approach would be to implement dispatch this way:
template<typename F, typename T>
auto dispatch(F && f, T && t, std::size_t i) -> decltype((std::forward<F>(f)(std::forward<T>(t)),0)) {
std::forward<F>(f)(std::forward<T>(t));
return 0;
}
template<typename F, typename T>
auto dispatch(F && f, T && t, std::size_t i) -> decltype((std::forward<F>(f)(std::forward<T>(t), i),0)) {
std::forward<F>(f)(std::forward<T>(t),i);
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With