3

I have an unknown type T that might be non-copy- or move-assignable, a function T op(const T &foo, Other bar) that computes and returns a new T based on an existing one, and a recursive function:

template<typename Iter, typename T>
T foldLeft(Iter first, Iter last, const T &z, std::function<T(T, Other)> op)
{
    if (first == last) {
        return z;
    }
    return foldLeft(std::next(first), last, op(z, *first), op);
}

The compiler can't always optimize the tail call, because T might have a non-trivial destructor. I'm trying to manually rewrite it with a loop, but can't figure out how to reassign to z.

5
  • If you are returning a T, then it must be copy or move constructible, so why would it not be copy or move assignable? I guess you will need to create a new object each time, maybe dynamically allocating it? Commented Apr 9, 2016 at 8:42
  • Like when T is std::unique_ptr<int>...? I want to do it on stack though, for better performance. Commented Apr 9, 2016 at 8:43
  • it's better to use an extra typename Fun template parameter instead of explicitly using std::fuction. That way, also regular functions and lambdas will work. Commented Apr 9, 2016 at 8:47
  • @TemplateRex Thanks, that's how I'm actually doing it. Here it's just a simplification. Commented Apr 9, 2016 at 8:48
  • You can explicitly new/delete into a buffer you provide, that could live on the stack, with char buffer[sizeof(T)];, but it'd be nasty. If you require copy construction, can you really not require copy assignment? Commented Apr 9, 2016 at 8:56

1 Answer 1

5

You may do the following, but the restriction is strange, and using std::accumulate seems simpler

template<typename Iter, typename T, typename Fn>
T foldLeft(Iter first, Iter last, const T &z, Fn op)
{
    std::aligned_storage_t<sizeof (T), alignof (T)> buf;
    T* res = new (&buf) T(z);
    for (auto it = first; it != last; ++it) {
        auto&& res2 = op(res, it);
        res->~T();
        res = new (&buf) T(std::move(res2));
    }
    T final_res = *res;
    res->~T();
    return final_res;
}

Note that it is not exception safe.

Sign up to request clarification or add additional context in comments.

2 Comments

Exactly what I needed. Thanks! I need to roll my own version of std::accumulate because it requires the initial value to be both CopyAssignable and CopyConstructible, which makes folding over something like a std::unique_ptr impossible.
If you work with std::unique_ptr, using move assign would be simpler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.