Ramneet Singh
Ramneet Singh

Reputation: 1

C++ NRVO in the presence of multiple return points

I have the following C++ code that returns a std::tuple<std::vector<int>, std::vector<int>>. My question is about copy elision and named return value optimisation. As I understand, since C++-17, the standard mandates that the return value will be copy-elided. So no temporary tuple will be created and then, (I assume) std::tie will directly initialise list1, list2. Now, whether it will call a copy or move constructor for the vectors or allocate space before function call and directly use that space depends on whether NRVO is performed. I would like to know whether NRVO is performed in this case, and if not, how I can change the code to achieve a single vector allocation for both lists (without passing them in as parameters).

std::tuple<std::vector<int>, std::vector<int>> parseInput(const std::string &filename) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        std::cerr << "Error: could not open file.\n";
        std::exit(1);
    }

    std::vector<int> list1, list2;
    int num1, num2;
    std::string line;
    while (std::getline(file, line)) {
        std::istringstream iss(line);
        if (iss >> num1) {
            if (iss >> num2) {
                list1.push_back(num1);
                list2.push_back(num2);
            } else {
                std::cerr << "Error: Line only contains one number. Exiting.\n";
                std::exit(1);
            }
        } else {
            std::cerr << "Warning: Line contains no numbers. Skipping.\n";
        }
    }
    
    return {list1, list2};
}

int main(int argc, char *argv[]) {
    ...
    std::vector<int> list1, list2;
    std::tie(list1, list2) = parseInput(f);
    ...
}

I tried to view this in Compiler Explorer (https://godbolt.org/z/oxP1z5c3a). As far as I can tell, neither copy elision nor NRVO is performed because the code for parseInput calls both vector and tuple constructors. I would like to understand why better, and know how to improve my code.

EDIT: The question was a bit unclear. There are two objects of interest here - the tuple and the vector. In my example, the tuple was only being allocated once because of RVO. But the vectors were being allocated twice and copied. It is possible to convert the move to a copy by replacing the return {list1, list2}; with return { std::move(list1), std::move(list2) } (suggested by @Jarod42). Another option (suggested by @Eljay) is to created a named tuple t in the beginning and return t. Here, NRVO will be performed and both the tuple and the vectors will only be allocated once. Here is a cleaned-up example showing this.

Upvotes: 0

Views: 112

Answers (0)

Related Questions