Does it makes sense to take vector<string_view> as a parameter

173 views Asked by At

I want a function that takes as input a vector of strings that I don't want to modify. So I thought about taking as input a vector<string_view>. However, if I do this, I can't pass a vector<string> as a parameter anymore.

Example:

#include <vector>
#include <string>
#include <string_view>

using namespace std;

void takesStringViewVectorRef(vector<string_view> const & v) {}

int main ()
{
  string_view strview; string str;
  takesStringViewVectorRef({"a", "b", strview, str}); // works, cool!

  vector<string> vec;
  takesStringViewVectorRef(vec); // doesn't work

  vector<const char *> vec2;
  takesStringViewVectorRef(vec2); // doesn't work either

  return 0;
}

Is this just a bad idea? Should I stick with vector<string>? I'm a little confused about this

2

There are 2 answers

5
Pepijn Kramer On BEST ANSWER

I would create a template function that can work on the string type you put in like this:

#include <iostream>
#include <vector>
#include <string>
#include <type_traits>

// using namespace std; <-- no don't do this
// some C++17 style type checking helpers

template<typename type_t>
constexpr bool is_stringlike_v = std::is_convertible_v<type_t, std::string_view>;

template<typename type_t>
using requires_stringlike_t = typename std::enable_if_t<is_stringlike_v<type_t>, void>;

// template function that accepts any stringlike type
template<typename type_t>
auto do_something_with_strings(const std::vector<type_t>& strings) -> requires_stringlike_t<type_t>
{
    for (const auto& string : strings)
    {
        std::cout << string << " ";
    }
    std::cout << "\n";
}

int main()
{
    std::string_view strview{ "strview" };
    std::string str{ "str" };

    // need to explicitly specialize since not all values in the "vector" are of the same type
    do_something_with_strings<std::string_view>({ "a", "b", strview, str }); // works, cool!

    std::vector<std::string> vec{ "std::string1", "std::string2" };
    do_something_with_strings(vec);

    std::vector<const char*> vec2{ "hello", "world" };
    do_something_with_strings(vec2);

    std::vector<int> vec3{1,2,3};
    //do_something_with_strings(vec3); <== will correctly not compile

    return 0;
}
2
HolyBlackCat On

There's no single best answer.

But one thing is for sure, you should pass std::span<const T> instead of const std::vector<T> &, this way your function will also work with arrays and other contiguous containers.

If you expect this function to be widely used, then std::span<const std::string_view> sounds like a more versatile option (I assume nothing inside the function needs an actual const std::string &).

You could add a second overload accepting std::span<const std::string>, and forwarding to the string_view overload internally.

But if you expect this function to be called only in a few times, and callers use std::vector<std::string>, then there's no harm in accepting std::span<const std::string>.


As for templating the function... If this is a pure string manipulation algorithm, sure. If not, I wouldn't do it just to get a nicer call syntax. Templating everything is a road to slower build times.