No operator+ for std::filesystem::path?
Asked Answered
P

4

12

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?

Pahl answered 27/1, 2021 at 11:3 Comment(8)
Nope. There is no other wayComstockery
@KamilCuk not possible with const char[N], that would be auto p2 = p1 / "B" / (std::string("C") + ".d");Kelso
("C" + ".d") adds two const char[2]sUneasy
@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"Kelso
note that in practice, "C" will most likely also be a std::filesystem::path, so the second option won't work. The first one is bizarre, but should work. :)Pahl
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".Hannahannah
For the reason, I guess someone will have to look into "the mailing list" or something similar. For being able to assign/ += to rvalue there's #65462389 . A path() call will help ((path(a) += b)) if the object inside is a lvalue.Nakada
@Hannahannah By "in practice", I meant, "in my real life application". This is a simplified example.Pahl
T
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).

Teary answered 27/1, 2021 at 15:10 Comment(1)
You can simply auto p2 = p1 / "B" / "C" += ".d"; No parentheses necessary due to operator precedence.Khosrow
S
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");
Stare answered 9/3, 2022 at 10:7 Comment(0)
V
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';
}
Virchow answered 8/11, 2021 at 5:41 Comment(0)
C
0

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…

Carolecarolee answered 27/9, 2023 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.