std::operat...asic_string)(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ’… std::operator+(std::basic_string) β€” the C++ Standard Library one

okay bestie, sit down, because this one is long and it's giving "the standard library said yes to everything." This is std::operator+(std::basic_string), live from <string>, and it's literally just the + operator for strings. Glue two strings together. That's the whole vibe. But because it's C++, there are sixteen overloads and a whole essay about allocators. We love that for us. πŸ’–


✨ Synopsis ✨

Defined in header <string>

Every overload is a template< class CharT, class Traits, class Alloc >. Here they are, all 16 of them, no skips, no cuts. πŸŽ€

(1) β€” constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( const std::basic_string<CharT,Traits,Alloc>& lhs,
           const std::basic_string<CharT,Traits,Alloc>& rhs );

(2) β€” constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( const std::basic_string<CharT,Traits,Alloc>& lhs,
           const CharT* rhs );

(3) β€” constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( const std::basic_string<CharT,Traits,Alloc>& lhs,
           CharT rhs );

(4) β€” since C++26

constexpr std::basic_string<CharT,Traits,Alloc>
operator+( const std::basic_string<CharT,Traits,Alloc>& lhs,
           std::type_identity_t<std::basic_string_view<CharT,Traits>> rhs );

(5) β€” constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( const CharT* lhs,
           const std::basic_string<CharT,Traits,Alloc>& rhs );

(6) β€” constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( CharT lhs,
           const std::basic_string<CharT,Traits,Alloc>& rhs );

(7) β€” since C++26

constexpr std::basic_string<CharT,Traits,Alloc>
operator+(
std::type_identity_t<std::basic_string_view<CharT,Traits>> lhs,
           const std::basic_string<CharT,Traits,Alloc>& rhs );

(8) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( std::basic_string<CharT,Traits,Alloc>&& lhs,
           std::basic_string<CharT,Traits,Alloc>&& rhs );

(9) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( std::basic_string<CharT,Traits,Alloc>&& lhs,
           const std::basic_string<CharT,Traits,Alloc>& rhs );

(10) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( std::basic_string<CharT,Traits,Alloc>&& lhs,
           const CharT* rhs );

(11) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( std::basic_string<CharT,Traits,Alloc>&& lhs,
           CharT rhs );

(12) β€” since C++26

constexpr std::basic_string<CharT,Traits,Alloc>
operator+( std::basic_string<CharT,Traits,Alloc>&& lhs,
           std::type_identity_t<std::basic_string_view<CharT,Traits>> rhs );

(13) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( const std::basic_string<CharT,Traits,Alloc>& lhs,
           std::basic_string<CharT,Traits,Alloc>&& rhs );

(14) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( const CharT* lhs,
           std::basic_string<CharT,Traits,Alloc>&& rhs );

(15) β€” since C++11, constexpr since C++20

std::basic_string<CharT,Traits,Alloc>
operator+( CharT lhs,
           std::basic_string<CharT,Traits,Alloc>&& rhs );

(16) β€” since C++26

constexpr std::basic_string<CharT,Traits,Alloc>
operator+(
std::type_identity_t<std::basic_string_view<CharT,Traits>> lhs,
           std::basic_string<CharT,Traits,Alloc>&& rhs );

🌸 What it even does 🌸

It returns a string containing the characters from lhs followed by the characters from rhs. That's it. That's the function. Concatenation. Iconic, honestly. ✨

Equivalent to: (this is the receipts, bestie β€” the standard literally tells you what it's doing under the hood πŸ‘€)

  1. and 2. β†’ std::basic_string<CharT, Traits, Allocator> r = lhs; r.append(rhs); return r;
  2. β†’ std::basic_string<CharT, Traits, Allocator> r = lhs; r.push_back(rhs); return r;
  3. β†’ std::basic_string<CharT, Traits, Allocator> r = lhs; r.append(rhs); return r;
  4. β†’ std::basic_string<CharT, Traits, Allocator> r = rhs; r.insert(0, lhs); return r;
  5. β†’ std::basic_string<CharT, Traits, Allocator> r = rhs; r.insert(r.begin(), lhs); return r;
  6. β†’ std::basic_string<CharT, Traits, Allocator> r = rhs; r.insert(0, lhs); return r;
  7. β†’ lhs.append(rhs); return std::move(lhs); β€” except that both lhs and rhs are left in valid but unspecified states. And if lhs and rhs have equal allocators, the implementation can move from either. πŸ’€ (unspecified state is giving "she's fine, don't worry about it")
  8. and 10. β†’ lhs.append(rhs); return std::move(lhs);
  9. β†’ lhs.push_back(rhs); return std::move(lhs);
  10. β†’ lhs.append(rhs); return std::move(lhs);
  11. and 14. β†’ rhs.insert(0, lhs); return std::move(rhs);
  12. β†’ rhs.insert(rhs.begin(), lhs); return std::move(rhs);
  13. β†’ rhs.insert(0, lhs); return std::move(rhs);

🧚 The allocator used for the result is:

  • 1–4) std::allocator_traits<Alloc>::select_on_container_copy_construction(lhs.get_allocator())
  • 5–7) std::allocator_traits<Alloc>::select_on_container_copy_construction(rhs.get_allocator())
  • 8–12) lhs.get_allocator()
  • 13–16) rhs.get_allocator()

πŸ¦‹ In other words (since C++11):

  • If one operand is a basic_string rvalue, its allocator is used. πŸ‘‘
  • Otherwise, select_on_container_copy_construction is used on the allocator of the lvalue basic_string operand.

In each case, the left operand is preferred when both are basic_strings of the same value category.

For (8–16), all rvalue basic_string operands are left in valid but unspecified states.


πŸŽ€ Parameters πŸŽ€

  • lhs β€” string, string view (since C++26), character, or pointer to the first character in a null-terminated array
  • rhs β€” string, string view (since C++26), character, or pointer to the first character in a null-terminated array

🌈 Return value 🌈

A string containing characters from lhs followed by the characters from rhs, using the allocator determined as above (since C++11).


πŸ’œ Notes πŸ’œ

operator+ should be used with great caution when stateful allocators are involved (such as when std::pmr::string is used) (since C++17). Prior to P1165R1, the allocator used for the result was determined by historical accident and can vary from overload to overload for no apparent reason. 😭 (not "no apparent reason" being an actual thing in the standard, I can't)

Because the allocator used by the result of operator+ is sensitive to value category, operator+ is not associative with respect to allocator propagation:

using my_string = std::basic_string<char, std::char_traits<char>, my_allocator<char>>;
my_string cat();
const my_string& dog();
my_string meow = /* ... */, woof = /* ... */;
meow + cat() + /* ... */; // uses select_on_container_copy_construction on meow's allocator
woof + dog() + /* ... */; // uses allocator of dog()'s return value instead

(since C++11)

meow + woof + meow;   // uses select_on_container_copy_construction on meow's allocator
meow + (woof + meow); // uses SOCCC on woof's allocator instead

For a chain of operator+ invocations, the allocator used for the ultimate result may be controlled by prepending an rvalue basic_string with the desired allocator:

// use my_favorite_allocator for the final result
my_string(my_favorite_allocator) + meow + woof + cat() + dog();

For better and portable control over allocators, member functions like append, insert, and operator+= should be used on a result string constructed with the desired allocator. 🫢

The usage of std::type_identity_t as parameter in overloads (4), (7), (12), and (16) ensures that an object of type std::basic_string<CharT, Traits, Allocator> can always be concatenated to an object of a type T with an implicit conversion to std::basic_string_view<CharT, Traits>, and vice versa, as per overload resolution rules. (since C++26)

⭐ Feature-test macro ⭐

  • __cpp_lib_string_view β€” value 202403 β€” Std (C++26) β€” Feature: Concatenation of strings and string views, overloads (4), (7), (12), (16)

part 1 out, part 2 loading… don't go anywhere bestie πŸ’…

πŸ’– Example πŸ’–

okay bestie, this is the full little demo β€” run it and vibe check the output:

// Run this code

        #include <iostream>
        #include <string>
        #include <string_view>

        int main()
        {
            std::string s1 = "Hello";
            std::string s2 = "world";
            const char* end = "!\n";
            std::cout << s1 + ' ' + s2 + end;

            std::string_view water{" Water"};
            #if __cpp_lib_string_view >= 202403
            std::cout << s1 + water + s2 << end; // overload (4), then (1)
            #else
            std::cout << s1 + std::string(water) + s2 << end; // OK, but less
       efficient
            #endif
        }

And the output is literally this, no cap:

        Hello world!
        Hello Waterworld!

🩹 Defect reports 🩹

These are the behavior-changing defect reports that got applied retroactively to previously published C++ standards. Very β€œwe fixed it after the fact, bestie” energy.

  • P1165R1 β€” applied to C++11; behavior as published: allocator propagation is haphazard and inconsistent; correct behavior: made more consistent.

πŸ‘€ See also πŸ‘€

  • operator+= β€” appends characters to the end (public member function)
  • append β€” appends characters to the end (public member function)
  • insert β€” inserts characters (public member function)

okay that's the whole page, bestie πŸ’…βœ¨ β€” source: cppreference.com, version/date: 2024.06.10, for std::operat...asic_string)(3). πŸŽ€

πŸ“¦ where this page comes from