2

I try to write a join path function with variadic template. Here's how i do it:

template<typename T>
T&& join_path (T&& path) {
    return path;
}

template<typename T, typename ... Args>
std::string join_path (T&& path1, Args&& ... paths)
{
    static_assert(std::is_same<typename std::decay<T>::type, std::string>::value ||
                  std::is_same<typename std::decay<T>::type, const char *>::value,
                  "T must be a basic_string");

    std::string p2 = join_path(std::forward<Args>(paths)...);
    if (!p2.empty() && p2[0] == '/')
        return path1 + p2;

    return path1 + '/' + p2;
}

But there's a problem, when I pass string literal like join_path("system", path) the T is consider as const char *. So I can't use +operator. How can I fix it?

One fix I think of is return std::string(path1) + '/' + p2;. But wouldn't it introduce extra copying?

Andriy Makukha
  • 7,580
  • 1
  • 38
  • 49
xubury
  • 84
  • 6

3 Answers3

4

You might use std::string_view since C++17:

std::string join_path(std::initializer_list<std::string_view> paths)
{
    std::string res;
    const char* sep = "";
    for (auto p : paths) {
        res += (!p.empty() && p[0] == '/') ? "" : sep
        res += p;
        sep = "/";
    }
    return res;
}

template<typename ... Ts>
std::string join_path (Ts&&... paths)
{
    static_assert(((std::is_same<typename std::decay<Ts>::type, std::string>::value ||
                  std::is_same<typename std::decay<Ts>::type, const char *>::value) || ...),
                  "T must be a basic_string");
    return join_path({paths...});
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • You might want to replace `res += sep` with `res += (!p.empty() && p[0]=='/') ? "" : sep`. Other than that, excellent fast solution! Much faster than the one I posted. – Andriy Makukha Nov 15 '20 at 08:46
  • 1
    Done, not sure what OP wants to handle with that, there are no checks for end of `path1`. – Jarod42 Nov 15 '20 at 08:53
  • Good, observation! I think, it's a mistake (that I missed as well...). But it seems like functionality needs to be similar to `path.join` in Python. – Andriy Makukha Nov 15 '20 at 08:56
  • Thanks, this works great! One question though, does it help to use `for (auto &p : paths)` or are they the same? – xubury Nov 15 '20 at 10:06
  • `std::string_view` is a small object (2 pointers), so by value is fine. – Jarod42 Nov 15 '20 at 10:27
  • I'm pretty sure the only reason the emptiness check is done is to account for the last recursion in the question's implementation, and not actually expecting a path passed to be empty. Great solution though. – Kostas Nov 15 '20 at 18:22
3

To avoid std::string(std::string) situation, take a look at this solution by Simple. It works faster than your solution and solution by Kostas.

Here is the solution modified for your use case:

inline std::string const& to_string(std::string const& s) { return s; }

template<typename... Args>
std::string join_path(Args const&... args)
{
    std::string result;
    std::string s;
    using ::to_string;
    using std::to_string;
    int unpack[]{0, (result += ((!(s = to_string(args)).empty() && s[0]=='/') ? "" : "/") + s, 0)...};
    static_cast<void>(unpack);
    return result;
}

Other than that, if you are worried about std::string(const char*), then I think it's not easily avoidable.

Andriy Makukha
  • 7,580
  • 1
  • 38
  • 49
  • 1
    [fold expression](https://en.cppreference.com/w/cpp/language/fold) of C++17 might replace `unpack` trick. – Jarod42 Nov 15 '20 at 08:38
2

You can explicitly cast the first parameter to std::string. You can make sure that's not repeated by using a fold expression(C++17):

template<class ... Strings>
std::string join_path (std::string path, Strings&& ... paths)
{
  path += ((std::string(paths) + '/') + ...);
  if constexpr (!sizeof...(paths))
    path1.pop_back(); // remove last /                                                                                         
  return path;
}

P.S. You can use std::decay_t<T> instead of typename std::decay<T>::type.

Kostas
  • 4,061
  • 1
  • 14
  • 32