6

I've seen code with std::string_view with the following signatures:

void foo(std::string_view const &); // 1
void foo(std::string_view const);   // 2

Which is more correct? Which is more efficient? (I assume the answer to both is one in the same)

nowi
  • 437
  • 4
  • 13
  • Not familiar with `std::string_view`, but I would imagine that the answer of correctness would largely depend on what you're doing. –  Mar 21 '20 at 03:50
  • https://stackoverflow.com/questions/40127965/how-exactly-is-stdstring-view-faster-than-const-stdstring – Michael Mar 21 '20 at 03:56

2 Answers2

7

Theoretically, as string_view is non-owning, it can already be considered a reference. So using by using a reference to a string_view, you get a reference to a reference.

But it actually seems to depend on the level of optimization you set the compiler to. Consider the following code:

#include <iostream>
#include <string_view>

void foo(std::string_view str) {
    std::cout << str;
}

void bar(std::string_view const &str){
    std::cout << str;
}

int main() {
    foo("test1");
    bar("test2");
}

foo should be more efficient than bar, right? If you compile with gcc 10.1 without optimizations, you get the following assembly for foo and bar:

foo(std::basic_string_view<char, std::char_traits<char> >):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     rax, rdi
        mov     rcx, rsi
        mov     rdx, rcx
        mov     QWORD PTR [rbp-16], rax
        mov     QWORD PTR [rbp-8], rdx
        mov     rdx, QWORD PTR [rbp-16]
        mov     rax, QWORD PTR [rbp-8]
        mov     rsi, rdx
        mov     rdx, rax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string_view<char, std::char_traits<char> >)
        nop
        leave
        ret
bar(std::basic_string_view<char, std::char_traits<char> > const&):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rax+8]
        mov     rsi, rdx
        mov     rdx, rax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::basic_string_view<char, std::char_traits<char> >)
        nop
        leave
        ret

You can see that foo has more operations than bar. Seems less efficient. However, if you set the compiler optimization to -O1, you get the following assembly

foo(std::basic_string_view<char, std::char_traits<char> >):
        sub     rsp, 8
        mov     rdx, rdi
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        add     rsp, 8
        ret
bar(std::basic_string_view<char, std::char_traits<char> > const&):
        sub     rsp, 8
        mov     rsi, QWORD PTR [rdi+8]
        mov     rdx, QWORD PTR [rdi]
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        add     rsp, 8
        ret

Now foo requires one less operation.

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
4

std::string_view acts as a pointer to a std::string or a char* C string. It contains a pointer and a length. There is no need to pass it by reference. Always use a value and copy it.

Never store it anywhere, or if you do remember it is a pointer, not the actual thing.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131