Does passing an std::optional<T> by reference actually save copying?
Asked Answered
M

3

10

I know that std::optional<T&> isn't supported in the standard. This question is about whether passing std::optional<T>& has any performance advantage

Sample code (https://godbolt.org/z/h56Pj6d6z) reproduced here

#include <ctime>
#include <iomanip>
#include <iostream>
#include <optional>

void DoStuff(std::optional<std::string> str) {
    if (str) std::cout << "cop: " << *str << std::endl;
}

void DoStuffRef(const std::optional<std::string>& str) {
    if (str) std::cout << "ref: " << *str << std::endl;
}

int main() {
    std::optional<std::string> str = {};
    DoStuff(str);
    DoStuffRef(str);
    str = "0123456789012345678901234567890123456789";
    DoStuff(str);
    DoStuffRef(str);
}

(My actual use case is an optional for a complex user-defined type, but I hope that a long string would do the same compiler-wise)

In this case, does DoStuffRef actually save any copying effort compared to DoStuff?

I tried to look at godbolt output but I don't know enough assembly to be sure. I do see that in the case of DoStuff, there seems to be a temp std::optional<T> created which is not present in DoStuffRef so my suspicion is that yes, passing an optional by reference save some copying

Appreciate the help!

Mahican answered 22/9, 2021 at 2:59 Comment(1)
Yes, you save a copy when the optional actually contains an string.Eldest
D
7

If you pass actual std::optional<std::string> then yes, there would be no copy. But if you pass just std::string then temporary optional has to be constructed first, resulting in a copy of the string.

Darceldarcey answered 22/9, 2021 at 3:3 Comment(4)
so in this case when I assign str to a string value and pass it on, since str is still of type std::optional<std::string>, I should save a copy by using DoStuffRef right?Mahican
I think in C++17 it won't create a copy because "(8)" in en.cppreference.com/w/cpp/utility/optional/optionalEldest
@Mahican exactly.Darceldarcey
@Eldest sure, if the std::string is rvalue, it will be a move instead if a copy, but the temporary will still be constructed.Darceldarcey
D
6

DoStuffRef saves an extra copy when argument is already std::optional<std::string> (as in your example).

But, if you pass directly a std::string, then in both cases, a std::optional should be constructed, involving copy/move constructor of the string.

Drugstore answered 22/9, 2021 at 8:4 Comment(0)
A
5

Passing std::optional<std::string> by value as in DoStuff will be slower any way since the std::optional<> represents the object and according to definition

Any instance of optional<T> at any given point in time either contains a value or
does not contain a value.

If an optional<T> contains a value, the value is guaranteed to be allocated as part
of the optional object footprint

The key here is the optional object footprint term

Let's say an emtpy std::string occupies 16 bytes - then optional for empty string will take at least 16 bytes. And passing optional by value will lead to 16 bytes copy all the time (excluding use case when compiler can pass it as rvalue & apply move semantic to entire optional object instance)

Take a look at representative benchmark for the provided use case:

    std::optional<std::string> str = {};
    DoStuff(str); // pass optional<> without value
    DoStuffRef(str); // still 2 times faster than above call

    std::optional<std::string> str = {""};
    DoStuff(str); // pass optional<> with value - empty string
    DoStuffRef(str); // still 7 times faster than above call

    str = "0123456789012345678901234567890123456789";
    DoStuff(str); // pass optional<> with big string
    DoStuffRef(str); //  60X times faster than above call

std::optional<> pass by value vs reference

Ascetic answered 23/9, 2023 at 1:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.