what is a correct way to call std::forward<Func> more than once?

Viewed 175

example: (it doesn't assume that func is pure, copy constructible or move constructible)

template<typename Func>
void dispatch(std::vector<int>& cont, Func&& func){
    for (auto& v : cont)
        std::forward<Func>(func)(v);
}

in a standard practice, we should receive a function object by Func&& and then call it by std::forward<Func>(func)(args...), to avoid any copy or move, and choose the correct overload of operator(). but there is a question: what happens if func is a rvalue and its operator() && moves itself?

obviously, a possible result is, the valid but indeterminate func is called, which is an unspecified behavior, I think.

so how to avoid this problem? I think copy constructibility and purity is not an acceptable cost. what about calling operator() & n - 1 times and operator() && once?

EDIT:

in this question, it doesn't depend on any real problem. it's just a thought experiment to find a more general way to deal with this situation.

and in the comment, I get some solutions:

  1. passing by copy. but Func is not necessarily copy constructible or move constructible. even if it is, the solution is equal to call operator() &, why not use func() directly?

  2. always calling operator() &. it seems like:

template<typename Func>
void dispatch(std::vector<int>& cont, Func&& func){
    for (auto& v : cont)
        func(v);
}

it's a good way to solve this problem, and always not give user any surprise. but I think it loses the rvalue property of func, and it may cause unnecessary copy:

template<typename Func>
void dispatch(std::vector<std::vector<int>>& cont, Func&& func){
    for (auto& vec : cont)
        func(vec);
}

template<typename T>
struct Assignable{
    T data;
    void operator()(T& v) const&{
        v = data;
    }
    void operator()(T& v) &&{
        v = std::move(data);
    }
};
template<typename T>
Assignable(T) -> Assignable<T>;

int main(){
    std::vector<int> a(10000, 100);
    std::vector<std::vector<int>> b(5);
    dispatch(b, Assignable{ std::move(a) });
}

the big vector a is actually copied 5 times, rather than copied 4 times and moved once, which is what we always do by enumeration. and in this case, I have no idea how to move a, unless don't use dispatch.

  1. n-1 times operator() & and once operator() &&
template<typename Func>
void dispatch(std::vector<std::vector<int>>& cont, Func&& func){
    for (auto begin = cont.begin(); begin + 1 < cont.end(); ++begin)
        func(*begin);
    if (!cont.empty())
        std::forward<Func>(func)(cont.back());
}

template<typename T>
struct Assignable{
    T data;
    void operator()(T& v) const&{
        v = data;
    }
    void operator()(T& v) &&{
        v = std::move(data);
    }
};
template<typename T>
Assignable(T) -> Assignable<T>;

int main(){
    std::vector<int> a(10000, 100);
    std::vector<std::vector<int>> b(5);
    dispatch(b, Assignable{ std::move(a) });
}

in this case, a is correctly moved. the rvalue property of func is considered.

but in the other hand, this style is also thought against the POLA (principle of least astonishment), which implies that the user is confused about why func works differently each time.

so I wonder if it is the best way to show the rvalue property of func?

EDIT2:

I think it can be transmitted to another question:

template<typename... Args>
void foo(Args&&...){};

template<typename... Args>
void invoke_twice(Args&&... args){
    foo(/* ??? */);
    foo(/* ??? */);
}

what should we do in this case? it's exactly wrong if forward twice. and if we specify this problem:

void foo(std::vector<int> const&){};
void foo(std::vector<int>&&){};

void invoke_twice(std::vector<int>&& cont){
    foo(/* ??? */);
    foo(/* ??? */);
}

it always is foo(cont) first, and then foo(std::move(cont)), isn't it?

in functional opinion, Func::operator()(...) const& means operator()(Func const&, ...), and Func::operator()(...) && means operator()(Func&&, ...), so can we say that we should use func(...) first and then std::forward<Func>(func)(...)?

0 Answers
Related