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)
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)
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.
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.