OK, here's my first serious attempt.
Is there something better than this?
#include <vector>
#include <iostream>
#include <string>
namespace notstd
{
/* deduce the traits of a container argument, even if it's an rvalue-reference */
template<class T>
struct container_traits
{
static_assert(not std::is_pointer<T>(), "");
using without_reference_type = std::remove_reference_t<T>;
using base_type = std::remove_cv_t<without_reference_type>;
static constexpr auto is_const = std::is_const<without_reference_type>::value;
static constexpr auto is_volaile = std::is_volatile<without_reference_type>::value;
using base_value_type = typename base_type::value_type;
using value_type = std::conditional_t<is_const, std::add_const_t<base_value_type>, base_value_type>;
};
template<class Function, class...Args>
struct is_compatible_function
{
template<class FArg> static auto test(FArg&& f) -> decltype(f(std::declval<Args>()...), void(), std::true_type());
static auto test(...) -> decltype(std::false_type());
static constexpr auto value = decltype(test(std::declval<Function>()))::value;
};
}
/**
* define the 2-argument algorithm, plus provide function compatibility checks
*/
template<class Vector, class Function>
struct over_op_2
{
using arg_1_type = std::add_lvalue_reference_t<typename notstd::container_traits<Vector>::value_type>;
using arg_2_type = std::size_t;
static constexpr auto is_compatible_function = notstd::is_compatible_function<Function, arg_1_type, arg_2_type>::value;
template<class VectorArg, class FunctionArg>
void operator()(VectorArg&& vec, FunctionArg&& f) const
{
std::size_t i = 0;
for (auto &&x : vec) {
f(x, i);
++i;
}
}
};
/**
* define the 1-argument algorithm, plus provide function compatibility checks
*/
template<class Vector, class Function>
struct over_op_1
{
using arg_1_type = std::add_lvalue_reference_t<typename notstd::container_traits<Vector>::value_type>;
static constexpr auto is_compatible_function = notstd::is_compatible_function<Function, arg_1_type>::value;
template<class VectorArg, class FunctionArg>
void operator()(VectorArg&& vec, FunctionArg&& f) const
{
for (auto &&x : vec) {
f(x);
}
}
};
/**
* Choose op_2 if the Function type will allow it, otherwise op_1 if that's possible, otherwise void (error)
*/
template<class Vector, class Function>
struct select_over_op
{
using op_1 = over_op_1<Vector, Function>;
using op_2 = over_op_2<Vector, Function>;
using type = std::conditional_t
<
op_2::is_compatible_function,
op_2,
std::conditional_t
<
op_1::is_compatible_function,
op_1,
void
>
>;
static_assert(not std::is_same<type, void>(), "function signatures are incompatible");
;
};
/**
* iterate over a vector-like container, calling f(elem, i) if available or f(elem) if not.
* @param vec is a reference to a vector-like object
* @param f is a function which is compatible with one of:
* void([const]value_type&, std::size_t), or
* void([const]value_type&)
*/
template<class Vector, class F>
decltype(auto) over(Vector &&vec, F &&f)
{
auto op = typename select_over_op<decltype(vec), decltype(f)>::type();
return op(std::forward<Vector>(vec), std::forward<F>(f));
}
int main() {
std::vector<double> v{4.1,5.1,6.1};
over(v, [] (auto x, auto y) { std::cout << x << ", " << y << "\n"; });
over(v, [] (auto && x, auto&& y) { std::cout << x << ", " << y << "\n"; });
over(v, [] (auto const& x, auto const& y) { std::cout << x << ", " << y << "\n"; });
over(v, [] (auto x, auto&& y) { std::cout << x << ", " << y << "\n"; });
over(v, [] (auto && x, auto&& y) { std::cout << x << ", " << y << "\n"; });
over(v, [] (auto const& x, auto&& y) { std::cout << x << ", " << y << "\n"; });
over(v, [] (auto x) { std::cout << x << "\n"; });
over(v, [] (auto const& x) { std::cout << x << "\n"; });
over(v, [] (auto && x) { std::cout << x << "\n"; });
// converting to int ok (but meh)
over(v, [] (int x) { std::cerr << x << "\n"; });
// converting to string correctly fails
// over(v, [] (std::string x) { std::cerr << x << "\n"; });
// const vector...
const std::vector<double> vc{4.1,5.1,6.1};
over(vc, [] (auto && x, auto&& y) { std::cout << x << ", " << y << "\n"; });
// breaking const contract on the value_type also fails
// over(vc, [] (double& x, auto&& y) { std::cout << x << ", " << y << "\n"; });
return 0;
}
http://coliru.stacked-crooked.com/a/cab94488736b75ed