How to Join a Vector of Strings with a Delimiter in C++23 Ranges
Understanding the Pipe Operator in C++23 Ranges
In C++23, the Ranges library introduces powerful tools for manipulating collections. When using the pipe operator (|), the range on the left-hand side is automatically passed as the first argument to the range adapter on the right-hand side.
In your initial attempt:
const string buf = configAbsoluteFilesNames | views::join_with(configAbsoluteFilesNames, string_view(", ")) | ranges::to<string>();You are passing configAbsoluteFilesNames twice: once via the pipe operator, and once as the first argument inside views::join_with. This mismatch leads to a compilation error because the compiler cannot find a matching overload.
The Solution: Correcting the Syntax
To fix this, you have two elegant options depending on your preferred style.
Option 1: Using the Pipe (|) Syntax (Recommended)
When piping, you only need to pass the delimiter to views::join_with. The vector itself is implicitly passed from the left side of the pipe:
using namespace std::literals; // for "sv" literal
const string buf = configAbsoluteFilesNames
| views::join_with(", "sv)
| ranges::to<string>();Option 2: Using the Function Call Syntax
If you prefer not to use the pipe operator for the join operation, you can pass both the container and the delimiter directly as arguments to views::join_with, and then pipe the result to ranges::to:
using namespace std::literals;
const string buf = views::join_with(configAbsoluteFilesNames, ", "sv)
| ranges::to<string>();Complete Working C++23 Example
Here is how your complete program should look. Note the use of the "sv" literal from <string_view> for clean, modern code:
#include <format>
#include <print>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
using namespace std;
using namespace std::literals;
int main() {
vector<string> configAbsoluteFilesNames = {
"/i_do_not_exist/a.file",
"/i_do_not_exist/b.file"
};
// Corrected pipe syntax
const string buf = configAbsoluteFilesNames
| views::join_with(", "sv)
| ranges::to<string>();
string message = format("Failed to read configuration file: {}", buf);
println("{}", message);
return 0;
}Why use std::views::join_with over manual loops?
- Declarative Code: It clearly expresses *what* you want to achieve rather than *how* to do it step-by-step, making the codebase much easier to read and maintain.
- Zero-Overhead Abstractions: C++ Ranges are designed to compile down to highly optimized assembly, often matching or beating manual loop performance.
- Type Safety: Integrating with
std::ranges::to<std::string>()ensures a seamless, type-safe conversion from a range of characters back into a standard string container.