返回不可复制对象的元组

时间:2019-05-12 19:13:16

标签: c++

请考虑以下内容:

#include <fstream>
#include <tuple>
#include <utility>
#include <vector>

const auto length_of_file = [](auto & file){
    file.seekg(0, std::ios::end);
    std::streampos length = file.tellg();
    file.seekg(0, std::ios::beg);
    return length;
};

int main(int, char * []) {
    const auto check_and_read = [](const auto & filename){
        std::ifstream file(filename, std::ios::binary);
        file.exceptions(std::ios::failbit | std::ios::badbit);
        std::vector<std::byte> data(length_of_file(file));
        file.read(reinterpret_cast<char*>(data.data()), data.size());
        return std::make_tuple(file, data);
    };
    auto [file, data] = check_and_read("foo.txt");
}

无法编译,因为它要复制file,这是不可能的。

return std::make_tuple(std::move(file), data);有效,但是我问自己»这是否意味着它正在复制而不是现在移动data?«,所以我宁愿有一个通用的解决方案。

但都不是(希望至少在这里可以使用移动语义/复制省略):

const auto check_and_read = [](const auto & filename)
-> std::tuple<std::ifstream, std::vector<std::byte>> {

…

return {file, data}

也(不是应该从右值引用的元组中进行移动构造吗?)

const auto check_and_read = [](const auto & filename)
-> std::tuple<std::ifstream, std::vector<std::byte>> {

…

return std::forward_as_tuple(file, data);

似乎可以正常工作。

是否有某种标准的方法可以确保在返回多个参数时无需单独std::move就可以进行移动构造?

3 个答案:

答案 0 :(得分:8)

请考虑以下内容:

std::tuple<std::string, std::string> foo() {
  std::string a = "hello";

  return {a, a};
}

您在特定表达式中使用filedata是隐式安全地移动的事实,即使对于非常相似的表达式,也不意味着总是如此。

编译器必须确定已命名的标识符注定要被视为r值,这样的分析很快就会变得不合理地复杂。

答案 1 :(得分:7)

没有标准功能,但是应该可以使用(C ++ 17):

template <class... Types>
auto move_to_tuple(Types&&... args) {
    return std::make_tuple(std::move(args)...);
}

答案 2 :(得分:2)

绝不会隐式执行移动,除非通常考虑省略但碰巧不可能的情况下作为回退。目前(至少据我所知),这意味着隐式移动仅在RVO和NRVO的回退时发生。

决不会考虑filedata的省略。首先,RVO和NRVO都不适用于它们。只考虑返回的元组,因为在这种情况下是RVO。因此:

return std::make_tuple(std::move(file), data);

将通过RVO消除元组,移动文件并复制数据。所以你应该这样做:

return std::make_tuple(std::move(file), std::move(data));