Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

template function that deduces both containers and initializer_list-s

I'd like to write a helper function like:

template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)
{
    using namespace std; // for cbegin/cend and ADL
    return pred(cbegin(left), cend(left), cbegin(right), cend(right));
}

It works well for containers:

std::vector<int> v1 = {1,2,3,4,5,6};
std::vector<int> v2 = {5,4,3,2,1,6};

std::cout << helper(v1, v2, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;

but it fails to deduce initializer_list-s (example):

std::cout << helper({1,2,3,4,5,6}, {5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;

Is there an idiomatic way to rewrite helper so that it deduces both containers and initializer_list-s?

I can't come up with anything better than overloads for all combinations of container and initializer_list.

like image 240
Dev Null Avatar asked Jul 31 '18 04:07

Dev Null


2 Answers

I think the fundamental problem here is that a braced-init-list like { 1, 2, 3 } is just an initializer and not an object of type std::initializer_list<T>. It can potentially be used to initialize an object of some given type. But it's not an object of any type itself. And there doesn't seem to be anything in the rules for function template argument deduction that would allow you to get an std::initializer_list<T> from a braced-init-list argument unless your function parameter was already declared to be some sort of std::initializer_list<T> to begin with.

So I'm afraid writing those overloads will be the simplest solution…

like image 124
Michael Kenzel Avatar answered Nov 01 '22 03:11

Michael Kenzel


Here is the best I can do:

template<class X>
struct Range {
  X* container;
  Range(X& x):container(std::addressof(x)) {}
  Range(X&& x):container(std::addressof(x)) {} // dangerous, but hey
  auto begin() const { using std::begin; return begin(*container); }
  auto end() const { using std::end; return end(*container); }
  auto cbegin() const { using std::cbegin; return cbegin(*container); }
  auto cend() const { using std::cend; return cend(*container); }
};

template<class T>
struct Range<std::initializer_list<T>> {
  using X=std::initializer_list<T>;
  X container;
  Range(X x):container(x) {}
  auto begin() const { using std::begin; return begin(container); }
  auto end() const { using std::end; return end(container); }
  auto cbegin() const { using std::cbegin; return cbegin(container); }
  auto cend() const { using std::cend; return cend(container); }
};

template<class T>
Range( std::initializer_list<T> ) -> Range<std::initializer_list< T >>;

template<class C1, class C2>
void foo( Range<C1> r1, Range<C2> c2 ) {}

test code:

Range r = {{'a', 'b', 'c'}};
(void)r;
std::vector v = {1,2,3};
foo( Range{{'a','b','c'}}, Range{v} );

you have to cast the arguments to Range manually for this to work at the call site, because class template arguement deduction doesn't work on function arguments.


We might be able to attack it differently.

template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)

change the above syntax to a chained-call.

helper(v1)({1,2,3})[pred];

that reduces the 2^n explosion into 2. Not much of a help with 2 overloads, but still...

template<class...Ts>
struct helper_t {
  std::tuple<Ts&&...> containers;
  template<class T>
  helper_t<T, Ts...> operator()(T&& t)&& {
    return { /* append-move containers and t into one tuple */ };
  }
  template<class T>
  helper_t<std::initializer_list<T>, Ts...> operator()(std::initializer_list<T> t)&& {
    return { /* append-move containers and t into one tuple */ };
  }
  template<class F>
  decltype(auto) operator[](F&& f)&& {
    return std::move(*this).apply_impl(
      std::make_index_sequence<sizeof...(Ts)>{},
      std::forward<F>(f)
    );
  }
private:
  template<std::size_t...Is, class F>
  decltype(auto) apply_impl( std::index_sequence<Is...>, F&& f ) && {
    using std::cbegin; using std::cend;
    using std::get;
    return std::forward<F>(f)(
      cbegin( get<Is>(std::move(containers)) ), cend( get<Is>(std::move(containers)) )
    );
  }
};
static constexpr const helper_t<> helper;

I left appending the tuples as an exercise.

helper( container1 )( {1,2,3} )( container2 )[ some_lambda ];

is the syntax.

like image 1
Yakk - Adam Nevraumont Avatar answered Nov 01 '22 05:11

Yakk - Adam Nevraumont