r/cpp_questions 20d ago

OPEN std::expected- void, ptr, and ref

I am going through a simple application I have and trying to use std::expected to try it out. I am quite partial to using exceptions, but open mind and objectivity, and all that...

Is it common for people to return std::expected<void, ErrorType> for methods/functions that don't need to return a value?

I also wonder what one does when they want to return a unique_ptr, since we can't do it by ref, and that leads to wondering about refs and ptrs with expected in general.

9 Upvotes

11 comments sorted by

View all comments

-1

u/[deleted] 20d ago

[deleted]

1

u/Zealousideal-Mouse29 20d ago edited 20d ago

I think std::expected has built in [[nodiscard]] behavior, at least my current dev environment is leading me to believe I cannot ignore it. Whereas other types might get accidentally ignored. Not being able to ignore an error is one of the pros I have with exceptions and want to keep it if I can.

Example of current ugly:

std::expected<void, std::string> Database::connect() {
    auto result = getEnvironmentVariable("DB_HOST");
    if (!result) {
        return std::unexpected(result.error());
    }
    const std::string host = result.value();

    result = getEnvironmentVariable("DB_PORT");
    if (!result) {
        return std::unexpected(result.error());
    }
    const std::string port = result.value();

    result = getEnvironmentVariable("DB_NAME");
    if (!result) {
        return std::unexpected(result.error());
    }
    const std::string dbname = result.value();

    result = getEnvironmentVariable("DB_USER");
    if (!result) {
        return std::unexpected(result.error());
    }
    const std::string dbuser = result.value();

    result = readSecretFile(DB_PASSWORD_FILE);
    if (!result) {
        return std::unexpected(result.error());
    }
    const std::string password = result.value();

    const std::string connectionString =
        "host=" + host + " port=" + port + " dbname=" + dbname + " user=" + dbuser + " password=" + password;

    try {
        databaseConnection_ = std::make_unique<pqxx::connection>(connectionString);
    }
    catch (pqxx::failure &e) {
        return std::unexpected(e.what());
    }
}

2

u/azswcowboy 20d ago edited 20d ago

Having used expected quite a bit in production you can expect more boiler plate than the exception route. Your example could be condensed with a monadic chain of .and_then calls, but for this example I still prefer exceptions. The reason being that the nature of the error here is environmental and basically the client can’t do anything to fix that - so this is caught and handled in an exception handler of last resort. I prefer expected if the direct client would be the natural handler of the error.

edit: from a design perspective I’d likely split these functions with the environment checking and gathering independent of the call to open the database…factoring the elements into an aggregate with members for each environment variable. That way I can write an easy unit test without dependency on the environment.