-1

I'm wondering about using the init-statement for if/switch with "placeholders" or anything like that, which are passed to a function as out- or in-out-parameters like e. g. T f(X& x).

As an example, the "brand new" C++17 noexcept overloads in std::filesystem uses an out parameter for passing an std::error_code by reference:

bool create_directory(const std::filesystem::path& p,
                      const std::filesystem::path& existing_p,
                      std::error_code& ec) noexcept;

or

void rename(const std::filesystem::path& old_p,
            const std::filesystem::path& new_p,
            std::error_code& ec) noexcept;

For a function returning the error code instead of passing it as an out parameter i would write (pseudo code):

namespace fs = std::filesystem;
if (auto ec = fs::rename(old_p, new_p); ec)
{
    // use ec handle error
} // ec's lifetime ends

Instead I cannot use it with an init-statement in my if clause:

namespace fs = std::filesystem;
std::error_code ec;
fs::rename(old_p, new_p, ec);
if (ec)
{
    // handle error
}
// ec is still there

Actually I work this around by using a wrapper to hold the caller's code clean:

namespace fs = std::filesystem;
inline std::error_code wrap_rename_ec(const fs::path& old_p,
                                        const fs::path& new_p) noexcept
{
    std::error_code ec;
    fs::rename(old_p, new_p, ec);
    return ec;
}

int main (void)
{
    if (auto ec = wrap_rename_ec("old_p", "new_p"); ec)
    {
         // use ec to handle error
    } // ec's lifetime ends
}

I'd find it nice to use something like this (pseudo code):

namespace fs = std::filesystem;
if (std::error_code ec, fs::rename(old_p, new_p, ec); ec)
{
    // handle error
} // ec's lifetime ends

I could use the other overload of std::filesystem::rename which throws an filesystem_error on error, but passing an existing path or file name as new path or file name does not have to be an error which has to break control flow in the program, I maybe want to append " (2)" or something, because I'm awaiting that case, which is not an exception.

Side note: My question aims not on discussing using exceptions over error codes or vice versa. ;-)

I'm not sure if cases like the one I described above have been taken into account at describing the init-statements for if/switch or have been discarded, while at least for me it would be nice to find a elegant way without wrapper functions handling out/in-out-parameters with init-statements for if/switch.

TL;DR: What is the most elegant way to take profit from init-statements for if/switch in combination with out/in-out parameters? Is it possible at all?

Regards, FA85

FA85
  • 45
  • 5
  • 4
    *"Side note: My question aims not on discussing using exceptions over error codes or vice versa"* - What *is* the question? The post contains just your musings. – StoryTeller - Unslander Monica Oct 20 '18 at 08:20
  • Related: https://stackoverflow.com/questions/15873188/returning-result-via-output-parameter-c-coding-standard – user202729 Oct 20 '18 at 08:21
  • I think you should not have return type `std::error_code&&` there – apple apple Oct 20 '18 at 08:21
  • IMO `std::error_code ec; fs::rename(old_p, new_p, ec); if (ec){}` is better readable than stuffing everything into a convoluted `if` statement. Just keep it that way. – zett42 Oct 20 '18 at 08:25
  • @StoryTeller: I have formulated my question in the first sentence and concreted in the last: What is the most elegant way to take profit from init-statements for if/switch in combination with out/in-out parameters. I'm sorry if I didn't make that clear enough. – FA85 Oct 20 '18 at 09:28
  • @zett42: IMO the readability suffers anyway if the init statement is too long. But you are right, when adding another or more statements in the init block the readability and clearness is hurt much more. I'll leave it as it is. Thank you! – FA85 Oct 20 '18 at 09:40

2 Answers2

1

I'd find it nice to use something like this (pseudo code):

using fs = std::filesystem;
if (std::error_code ec, fs::rename(old_p, new_p, ec); ec)
{
    // handle error
} // ec's lifetime ends

You almost guessed the right syntax.

You're looking for this:

using fs = std::filesystem;
if (std::error_code ec; fs::rename(old_p, new_p, ec), ec) // Note that , and ; were switched.
{
    // ...
}

Also, note that using your wrapper causes UB due to accessing a dangling reference:

using fs = std::filesystem;
inline std::error_code&& wrap_rename_ec(const fs::path& old_p,
                                        const fs::path& new_p) noexcept
{
    std::error_code ec; // Lifetime of `ec` ends after the function returns.
    fs::rename(old_p, new_p, ec);
    return std::move(ec);
}

The correct way to write that would be:

using fs = std::filesystem;
std::error_code wrap_rename_ec(const fs::path& old_p,
                                        const fs::path& new_p) noexcept
{
    std::error_code ec;
    fs::rename(old_p, new_p, ec);
    return ec;
}
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
0

I think you are asking yourself the right questions.

The idea of the init-statement is to initialize a new variable. If you need more than one statement to initialize the variable, then you probably want to refactor the code, and what you did is one option.

So there is a compromise here:

  • you want something concise

  • you want to be able to create one variable that has a lifetime of the if or for

The committee probably discussed allowing more than one statement, but they decide it would burden readability without helping code quality (at least that's my understanding).

Still, small mistake:

return std::move(ec);

Rreturn ec directly and not by &&, by value. The compiler probably tells you as much.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62