14

It is possible to append multiple paths in a row using the / operator:

  std::filesystem::path p1{"A"};
  auto p2 = p1 / "B" / "C";

which is rather convenient. However, concat only offers +=:

  std::filesystem::path p1{"A"};
  auto p2 = p1 / "B" / "C" + ".d";   // NOT OK

This is pretty annoying, as I can't easily add extensions to the end of my paths. I have no choice but to write something like

  std::filesystem::path p1{"A"};
  auto p2 = p1 / "B" / "C";
  p2 += ".d";

Am I missing something? Is there a reason for this inconsistency?

8
  • 4
    Nope. There is no other way Commented Jan 27, 2021 at 11:07
  • @KamilCuk not possible with const char[N], that would be auto p2 = p1 / "B" / (std::string("C") + ".d"); Commented Jan 27, 2021 at 11:16
  • ("C" + ".d") adds two const char[2]s Commented Jan 27, 2021 at 11:17
  • @KamilCuk a string literal like "A" is of type "const char[N]" and will be converted to "const char*" when using operator +, so you're adding two pointers with "A"+"B" Commented Jan 27, 2021 at 11:17
  • 1
    sooo. auto p2 = p1 / "B" / "C" += ".d"; or auto p2 = p1 / "B" / (std::string() + "C" + ".d"); :D . Both of the these work in godbolt. note that in practice, "C" will most likely Not in practice, in any case the result of p1 / "B" / "C" has type std::filesystem::path, so it's doing += on it. Still, this is not an answer, as I do not know "the reason for this inconsistency". Commented Jan 27, 2021 at 11:21

4 Answers 4

4

This is a bit speculative, but I think the reason for this is that an operator+ could be easily confused with operator/. This would then lead to bugs if used as follows

path p2{"/"};
auto p1 = p2 + "A" + "B";
// Wants /A/B, gets /AB

Using string literals makes the workaround nicer:

using namespace std::literals;
std::filesystem::path p1{"A"};
auto p2 = p1 / "B" / ("C"s + ".d");   

Here, "C"s creates a std::string with content C and then we use std::string's operator+. If the "C" part is itself already a path (otherwise you could just write "C.d" to begin with), you can do

std::filesystem::path p1{"A"}, c_path{"C"};
auto p2 = p1 / "B" / (c_path += ".d");   

since operator+= returns the resulting object. (This is a bit wasteful but I can imagine that the compiler will optimize that).

Sign up to request clarification or add additional context in comments.

1 Comment

You can simply auto p2 = p1 / "B" / "C" += ".d"; No parentheses necessary due to operator precedence.
2

There's no such operator, but actually it's pretty easy to add such one on one's own; below is the quick and dirty variant coming along as a template (safer might be individual overloads matching those of operator+=, not going that fare here, though):

template <typename T>
std::filesystem::path operator+(std::filesystem::path path, T&& data)
// (accepting by value, we're going to modify!)
{
    path += std::forward<T>(data);
    return path;
}

Sure, no perfect forwarding necessary either, all operators of path accept by value or const reference anyway, but who knows if that changes any day…

If that's a good idea (see n314159 suspecting it might have been avoided by C++ committee to prevent confusing it with the other operator) – well, abstaining from reasoning about…

Comments

1

If all what you need is to add extensions, you can also do this:

std::filesystem::path p1{"A"};
auto p2 = (p1 / "B" / "C").replace_extension(".d");

Comments

0

I though maybe it was because std::string and const char * implicitly convert to std::filesystem::path. Here is some c++20 being very careful to avoid unwanted implicit conversions.

#include <concepts>
#include <filesystem>
#include <iostream>
#include <string>
using namespace std::string_literals;
template <typename T, typename U>
requires std::same_as<std::decay_t<T>, std::filesystem::path> &&
    (!std::same_as<std::decay_t<U>, std::filesystem::path>) && 
        std::convertible_to<std::decay_t<U>, std::filesystem::path>
    inline std::filesystem::path
        operator+(const T &lhs, const U &rhs) {
    auto tmp = lhs;
    tmp += rhs;
    return tmp;
}

template <typename T>
requires std::same_as<std::decay_t<T>, std::filesystem::path> ||
    std::same_as<std::decay_t<T>, std::filesystem::directory_entry>
inline std::filesystem::path operator+(const std::filesystem::path &lhs,
                                       const T &rhs) {
    auto tmp = lhs;
    tmp += rhs;
    return tmp;
}
int main() {
    std::cout << "aaa" + std::filesystem::path{"bbb"} << '\n';
    std::cout << "aaa"s + std::filesystem::path{"bbb"} << '\n';
    std::cout << std::filesystem::directory_entry{"aaa"} +
                     std::filesystem::path{"bbb"}
              << '\n';

    std::cout << std::filesystem::path{"aaa"} + "bbb" << '\n';
    std::cout << std::filesystem::path{"aaa"} + "bbb"s << '\n';
    std::cout << std::filesystem::path{"aaa"} +
                     std::filesystem::directory_entry{"bbb"}
              << '\n';

    std::cout << std::filesystem::path{"aaa"} + std::filesystem::path{"bbb"}
              << '\n';
    std::cout << std::filesystem::directory_entry{"aaa"} +
                     std::filesystem::directory_entry{"bbb"}
              << '\n';
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.