2

Sometime ago in a code-review (C++) I suggested to change the input argument from Path type to Optional<Path>, where the function has specific logic for unset path. It looks for me intuitively better, but the author appealed that Path::empty() (empty path) method should semantically mean the same as unset Optional.

My sole rational argument is that empty path may also be interpreted as the current working directory. But then I also thought that . may be used as CWD as well.

What is a good default semantic for a cross-platform API path emptiness? E.g. . may be not so common outside *nix OSes, or there are already some common semantics in any popular programming language.

Nicol Bolas
  • 11,813
  • 4
  • 37
  • 46
abyss.7
  • 135
  • 2
  • https://en.cppreference.com/w/cpp/filesystem/path Note that `.` must represent the current directory. Systems which handle it differently must still support `.` as the current directory. – Justin Jan 14 '19 at 17:11
  • @Justin good point! – abyss.7 Jan 14 '19 at 17:17
  • 2
    I would say that `optional` is very different from `path`, just like how `optional` is very different from `string`. It may be that the empty `path` is what you want, or it may be that it doesn't work for the case and you need an `optional`. It depends – Justin Jan 14 '19 at 17:20
  • There's another option: having a second function `doFooWithoutPath()`, which delegates the responsibility to the caller to call the correct method. I'm personally not fond of methods accepting `optional` parameters (you know at the call site that you don't have a value), and I don't believe `Path::empty()` convey enough information in this case (why would an empty path means that it is unset?). – Vincent Savard Jan 15 '19 at 15:54
  • @VincentSavard: "*you know at the call site that you don't have a value*" Do you? What if you have a function that searches for a file and returns an `optional` which is `nullopt` if no file was found? Unless you check that value, you don't know if the path exists. And maybe your local code doesn't care; why should you not pass that `optional` to others? – Nicol Bolas Jan 15 '19 at 17:44
  • @NicolBolas Of course you do know, by definition when you pass an `optional` to a function, you can know whether it is present or not. I haven't worked with `std::optional`, but those types usually have a `map` method which automatically unbox the value and pass it to the function if possible, otherwise it returns `nullopt`. For instance, in Java, you would do `maybe_path.map(Foo::doFoo).orElseGet(() -> doFooWithoutPath())`. It keeps your original function easily composable, and you don't have to create a useless `optional` if you have to call it and you know you have a path. – Vincent Savard Jan 15 '19 at 17:59
  • @VincentSavard: I fail to see how that `map` version is better than `doFoo(maybe_path)`. Especially if `maybe_path` is a complex expression. And even moreso if `doFoo` needs more parameters than just a path; even calling `obj.doFoo` is impossible without creating a lambda or adding other verbosity. There is nothing fundamentally wrong with passing around `optional`s as is. – Nicol Bolas Jan 15 '19 at 18:30
  • 1
    @NicolBolas You're right, there's no fundamental issues. My personal preference is that it's easier to compose a function that takes actual parameters for which you don't need to check presence. You could argue that you need a function which accepts an `optional`. Then why would you not accept a method which takes an `array` of `Paths` and loop over it? What about other kind of collections? Where do you trace the line? In my experience, I'd rather have the simplest function which do not require extraneous validation because I know I'll be able to compose it for my other use cases. – Vincent Savard Jan 15 '19 at 18:37

3 Answers3

4

A function which takes an optional<path> makes it abundantly, unquestionably clear that it's OK for the user to not provide a path. A function which takes a path may or may not have it be OK for a real path to be provided. You have to look it up in the docs to know for sure.

However, if you have an API where many functions take optional<path> and many functions that take path, then the user can probably infer that any function that takes path directly is a function that won't work if that path is empty. So consistent use of optional for such types has a purpose.

Nicol Bolas
  • 11,813
  • 4
  • 37
  • 46
1

From the draft standard [fs.path.generic/3]:

The dot filename is treated as a reference to the current directory.

So you always have an unambiguous "current directory" path. I would be surprised by a system that used an empty path to mean the same as ..

Caleth
  • 10,519
  • 2
  • 23
  • 35
0

What is a good default semantic for a cross-platform API path emptiness?

std::optional<path> is a good way to represent "path argument is optional". Another good way is signature polymorphism (provide two entry points):

void your_api(int other_args, std::filesystem::path& the_path);

void your_api(int other_args);
utnapistim
  • 5,285
  • 16
  • 25