Lingxi
Lingxi

Reputation: 14987

Implement function template to fill multi-dimensional objects

Filling multi-dimensional objects (arrays, nested standard containers, etc) in C++ has always been annoying to me. Nested loops are usually used. For example, to fill a 3-dimensional object obj with value v, you may write something like

for (auto& x : obj) {
  for (auto& y : x) {
    for (auto& z : y) {
      z = v;
    }
  }
}

Such loops are code noise, being tedious to write and also hamper code reading. I'm thinking about writing a function template to perform the filling. Ideally, with such a function template, you should be able to write something like

fill(obj, v);

Any ideas?

Currently, I have a function template make_multi() to make multi-dimensional objects. So you can do

// assuming `obj` is a 3x3x3 nested `std::vector`
obj = make_multi<std::vector>(v, 3, 3, 3);

Besides being more code than the ideal case, this solution is a performance nightmare. Ultimately, I have to look for better ways.

Upvotes: 2

Views: 91

Answers (2)

ildjarn
ildjarn

Reputation: 62985

Here is the greedy counterpart to @Columbo's excellent answer:

namespace detail {
    using std::begin;
    using std::end;

    template<typename Elem, typename Func>
    constexpr void flat_foreach(Elem&& e, Func& f, long) {
        f(std::forward<Elem>(e));
    }

    template<typename Range, typename Func>
    constexpr auto flat_foreach(Range&& r, Func& f, int)
      -> decltype(begin(r), void(end(r))) {
        for (auto&& i : r) {
            flat_foreach(std::forward<decltype(i)>(i), f, 0);
        }
    }
}

template<typename Range, typename Func>
constexpr void flat_foreach(Range&& r, Func f) {
    detail::flat_foreach(std::forward<Range>(r), f, 0);
}

Online Demo

The general advantage to this approach is that one can pass in a generic lambda/functor for f.

Upvotes: 1

Columbo
Columbo

Reputation: 60999

You can write a flattened foreach:

namespace detail {
    template <typename Range, typename Func>
    constexpr auto flat_foreach(Range&& r, Func& f, int)
        -> decltype(void(f(std::forward<Range>(r)))) {f(std::forward<Range>(r));}

    template <typename Range, typename Func>
    constexpr void flat_foreach(Range&& r, Func& f...) {
        for (auto&& i : r)
            flat_foreach(std::forward<decltype(i)>(i), f, 0);
    }
}

template <typename Range, typename Func>
constexpr void flat_foreach(Range&& r, Func f) 
{detail::flat_foreach(std::forward<Range>(r), f, 0);}

Demo. This is the non-greedy approach, passing the range off to the given function as shallow as possible.

Upvotes: 6

Related Questions