vendredi 14 juillet 2023

What could be the good way to check if N optionals returning from different functions all have values

Let say I have a code like that

#include <string>
#include <optional>
#include <charconv>

std::optional<int> get_1(const std::string& s) {  return s == "A" ? 1 : std::optional<int>{}; }
std::optional<const char*> get_2(const std::string& s) {  return s == "A" ? "2" : std::optional<const char*>{}; }
std::optional<double> get_3(const std::string& s) {  return s == "A" ? 3.0 : std::optional<double>{}; }
std::optional<int64_t> get_4(const std::string& s) {  return s == "A" ? -10000000000 : std::optional<int64_t>{}; }
std::optional<std::string> get_5(const std::string& s) {  return s == "A" ? "5" : std::optional<std::string>{}; }
// ..... more functions like that

struct S {
    int a1;
    const char* a2;
    double a3;
    int64_t a4;
    std::string a5;
private:
    int AGR_INIT_IS_NOT_ALLOWED{0};
};

std::optional<S> getS() {
    S s;
    // Now I'd like to return std::nullopt if any of the following calls return std::nullopt
    // The naive way to do that is smth like that

    if (auto val = get_1("A"); val) {
        s.a1 = *val;
    } else {
        return std::nullopt;
    }

    if (auto val = get_2("A"); val) {
        s.a2 = *val;
    } else {
        return std::nullopt;
    }

    if (auto val = get_3("A"); val) {
        s.a3 = *val;
    } else {
        return std::nullopt;
    }

    if (auto val = get_4("A"); val) {
        s.a4 = *val;
    } else {
        return std::nullopt;
    }

    if (auto val = get_5("A"); val) {
        s.a5 = *val;
    } else {
        return std::nullopt;
    }
    
    return s;
}

int main()
{
    return !getS();
}

Now my question is, can anyone advice on how to get rid of code replication inside getS function? Any pattern or smth that would work here?

The first obvious "solution" is instead of returning an optional just throw an exception inside get_* and then catch it in getS

#include <string>
#include <optional>
#include <charconv>
#include <exception>

int get_1(const std::string& s) {  return s == "A" ? 1 :throw std::exception{};}
const char* get_2(const std::string& s) {  return s == "A" ? "2" : throw std::exception{}; }
double get_3(const std::string& s) {  return s == "A" ? 3.0 : throw std::exception{}; }
int64_t get_4(const std::string& s) {  return s == "A" ? -10000000000 : throw std::exception{}; }
std::string get_5(const std::string& s) {  return s == "A" ? "5" : throw std::exception{}; }
// ..... more functions like that

struct S {
    int a1;
    const char* a2;
    double a3;
    int64_t a4;
    std::string a5;
private:
    int AGR_INIT_IS_NOT_ALLOWED{0};
};

std::optional<S> getS() {
    S s;
    try {
        s.a1 = get_1("A");
        s.a2 = get_2("A");
        s.a3 = get_3("A");
        s.a4 = get_4("A");
        s.a5 = get_5("A");
    } catch (const std::exception&) {
        return std::nullopt;
    }
    return s;
}

int main()
{
    return !getS();
}

I can't use exceptions, so it's not a solution at all (not to mention it is silly to refactor all these methods). Though, for now to me it looks like the best option it terms of avoiding boilerplate.

The second "solution" I was thinking about is to put all the optionals into tuple first, check if all them hold values and then assign from the tuple like that:

#include <string>
#include <optional>
#include <tuple>


std::optional<int> get_1(const std::string& s) {  return s == "A" ? 1 : std::optional<int>{}; }
std::optional<const char*> get_2(const std::string& s) {  return s == "A" ? "2" : std::optional<const char*>{}; }
std::optional<double> get_3(const std::string& s) {  return s == "A" ? 3.0 : std::optional<double>{}; }
std::optional<int64_t> get_4(const std::string& s) {  return s == "A" ? -10000000000 : std::optional<int64_t>{}; }
std::optional<std::string> get_5(const std::string& s) {  return s == "A" ? "5" : std::optional<std::string>{}; }
// ..... more functions like that

struct S {
    int a1;
    const char* a2;
    double a3;
    int64_t a4;
    std::string a5;
private:
    int AGR_INIT_IS_NOT_ALLOWED{0};
};

std::optional<S> getS() {
    auto t = std::make_tuple(get_1("A"), get_2("A"), get_3("A") ,get_4("A") ,get_5("A"));
    if (!std::apply([](auto&&... vals){ return ([](auto&& v){ return v.has_value(); }(vals) && ...);}, t)) {
        return std::nullopt;
    }

    S s;
    s.a1 = *std::get<0>(t);
    s.a2 = *std::get<1>(t);
    s.a3 = *std::get<2>(t);
    s.a4 = *std::get<3>(t);
    s.a5 = *std::get<4>(t);
    return s;
}

int main()
{
    return !getS();
}

That's not nice as we've got to execute all the get_* functions, even if the first one alrede returned std::nullopt (I also don't like to use std::get here)

And here I pretty much ran out of any good ideas how to achieve "exception based approach" w/o any exceptions.

Aucun commentaire:

Enregistrer un commentaire