多次调用 std::forward<Func> 的正确方法是什么?

Red*_*Fog 5 c++ templates language-lawyer perfect-forwarding c++11

示例:(它不假设func是纯的、可复制构造的或可移动构造的)

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

在标准实践中,我们应该通过 接收一个函数对象,Func&&然后通过 调用它std::forward<Func>(func)(args...),以避免任何复制或移动,并选择 的正确重载operator()。但有一个问题:如果是一个右值并且它本身移动会发生什么?funcoperator() &&

显然,一个可能的结果是,有效但不确定的 func被调用,我认为这是一种未指定的行为。

那么如何避免这个问题呢?我认为复制构造性和纯度不是可接受的成本。operator() &调用n - 1 次和一次怎么样operator() &&

编辑:

在这个问题中,它不依赖于任何实际问题。这只是一个思想实验,旨在找到一种更通用的方法来处理这种情况。

在评论中,我得到了一些解决方案:

  1. 路过副本。但Func不一定是可复制构造或可移动构造的。即使是,解决方案也等于 call operator() &,为什么不func()直接使用呢?

  2. 总是打电话operator() &。这好像是:

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

这是解决这个问题的好方法,而且总是不会给用户带来任何惊喜。但我认为它失去了 的右值属性func,并且可能会导致不必要的复制:

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) });
}
Run Code Online (Sandbox Code Playgroud)

大向量a实际上被复制了 5 次,而不是复制 4 次并移动一次,这就是我们通常通过枚举所做的事情。在这种情况下,我不知道如何移动a,除非不使用dispatch

  1. n-1次operator() &和一次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) });
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,a已正确移动。func考虑 的右值属性。

但另一方面,这种风格也被认为违背了 POLA(最小惊讶原则),这意味着用户对为什么func每次的工作方式不同感到困惑。

所以我想知道这是否是显示 的右值属性的最佳方式func

编辑2:

我觉得可以转移到另一个问题:

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

template<typename... Args>
void invoke_twice(Args&&... args){
    foo(/* ??? */);
    foo(/* ??? */);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下我们应该做什么?forward如果两次就完全错误了。如果我们指定这个问题:

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

void invoke_twice(std::vector<int>&& cont){
    foo(/* ??? */);
    foo(/* ??? */);
}
Run Code Online (Sandbox Code Playgroud)

它总是foo(cont)先,然后foo(std::move(cont)),不是吗?

从功能的角度来看,Func::operator()(...) const&手段operator()(Func const&, ...)Func::operator()(...) &&手段operator()(Func&&, ...),那么我们是否可以说我们应该func(...)先使用然后再使用std::forward<Func>(func)(...)