Есть ли способ переключения между std :: array - PullRequest
2 голосов
/ 21 ноября 2019

У меня есть какой-то устаревший код, написанный на языке C, и я хотел бы провести рефакторинг на C ++ 14. Я должен столкнуться с проблемой, которую не могу решить.

В старом простом C был способ переключения между массивами.

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

const int sz = (machine == 1) ? NELEMS (errors1) : NELEMS (errors2);
const struct Error *error = (machine == 1) ? &errors1[0] : &errors2[0];

Код выше работает, нокак-то не читается. Я хотел бы использовать std::array, потому что им легко манипулировать с меньшим количеством читаемого кода. std::vector использует память кучи, и я хотел бы, чтобы результат был ПЗУ.

Тернарный оператор кода C ++ const Error &error = (machine == 1) ? errors1 : errors2; его нельзя использовать таким образом, поскольку std::array<Error, 3> errors1и std::array<Error, 5> errors2 разные типы! так как же можно достичь той же функциональности с std::array?

Legacy C code

enum ErrorCode {

  ERR_NOT_FOUND,
  ERR_FORBIDDEN,
  ERR_INTERNAL,
  ERR_UNAVAILABLE,
  ERR_NO_RESPONSE,
  ERR_UNSUPPORTED,
  ERR_OUT_OF_MEMORY,
  ERR_TIMEOUT
};

struct Error {
  int id;
  int code;
  const char *str;
};



static const struct Error errors1[] = {
    {1001, ERR_NOT_FOUND, "Not Found"},
    {1002, ERR_FORBIDDEN, "Forbidden"},
    {1003, ERR_INTERNAL, "Internal Error"}
};



static const struct Error errors2[] = {
  {1004, ERR_UNAVAILABLE, "Temporarly Unavailable"},
  {1005, ERR_NO_RESPONSE, "No Response"},
  {1006, ERR_UNSUPPORTED, "Unsupported"},
  {1007, ERR_OUT_OF_MEMORY, "Insufficient Memory"},
  {1008, ERR_TIMEOUT, "Timeout"}
};

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

char *error_to_string (char machine, int id) {
    const int sz = (machine == 1) ? NELEMS (errors1) : NELEMS (errors2);
    const struct Error *error = (machine == 1) ? &errors1[0] : &errors2[0];
    const char *str = "Unknown";

    for (int i = 0; i < sz; i++) {
        if (error[i].id == id) {
           str = error[i].str;
           break;
        }
    }
    return str;
}

попытка получить модеренный код C ++ с той же функциональностью, что и в C code

std::array<Error, 3> errors1= {{
    {1001, ERR_NOT_FOUND, "Not Found"},
    {1002, ERR_FORBIDDEN, "Forbidden"},
    {1003, ERR_INTERNAL, "Internal Error"}
}};

std::array<Error, 5> errors2= {{
    {1004, ERR_UNAVAILABLE, "Temporarly Unavailable"},
    {1005, ERR_NO_RESPONSE, "No Response"},
    {1006, ERR_UNSUPPORTED, "Unsupported"},
    {1007, ERR_OUT_OF_MEMORY, "Insufficient Memory"},
    {1008, ERR_TIMEOUT, "Timeout"}
}};

const char *error_to_string (char machine, int id) {
    const char *str = (char*) "Unknown";
    const auto errors = (machine == 1) ? &errors1[0] : &errors2[0];

     for (auto error : errors) {
        if (error.id == id) {
            str = error.str;
            break;
        }
     }
    return str;
}

Ответы [ 3 ]

3 голосов
/ 21 ноября 2019

Вы можете использовать лямбду, которая ищет ошибку в данном массиве.

const char *error_to_string(const char machine, const int id) {
    const auto find_error = [id](const auto &errors) {
        for (const Error &error : errors) {
            if (error.id == id) return error.str;
        }
        return "Unknown";
    };
    return machine == 1 ? find_error(errors1) : find_error(errors2);
}

Чтобы расширить это на множество массивов, просто используйте switch.

const char *error_to_string(const char machine, const int id) {
    const auto find_error = [id](const auto &errors) {
        for (const Error &error : errors) {
            if (error.id == id) return error.str;
        }
        return "Unknown";
    };
    switch (machine) {
        case 1: return find_error(errors1);
        case 2: return find_error(errors2);
        case 3: return find_error(errors3);
        // ...
    }
    // probably want to handle this properly
    assert(false);
    return "Invalid machine";
 }
2 голосов
/ 21 ноября 2019

В C ++ 20 есть std::span:

const char* error_to_string (char machine, int id) {
    const auto errors = (machine == 1) ?
                            std::span<Error>{errors1} :
                            std::span<Error>{errors2};

     for (const auto& error : errors) {
        if (error.id == id) {
            return error.str;
        }
     }
    return "Unknown";
}

Ранее вы могли использовать шаблон:

template <typename Container>
const char* error_to_string(const Container& errors) {
     for (const auto& error : errors) {
        if (error.id == id) {
            return error.str;
        }
     }
    return "Unknown";
}

const char* error_to_string (char machine, int id) {
    return (machine == 1) ? error_to_string(errors1) : error_to_string(errors2);
}
2 голосов
/ 21 ноября 2019

Если у вас есть N массивы, может помочь не только два, простое метапрограммирование:

template<char machine_id>
auto& get_errors() {
    if constexpr (machine_id == 0) 
        return errors1;
    else if constexpr (machine_id == 1) 
        return errors2;
    ...
    else
        return errorsN;
}

template<char machine_id>
const char* error_to_string_impl(char machine, int id) {
    if (machine == machine_id) {
        const auto& errors = get_errors<machine_id>();
        const auto fn = [id](Error e) { return e.id == id; };

        const auto it = std::find_if(errors.begin(), errors.end(), fn);
        if (it != errors.end())
            return it->str;
        return "Unknown";
    }

    if constexpr (machine_id + 1 < N)
        return error_to_string_impl<machine_id + 1>(machine, id);

    assert(false); // unreachable
    // return "Unknown machine";
}

const char* error_to_string(char machine, int id) {
    return error_to_string_impl<0>(machine, id);
}

get_error также может быть реализовано с перегрузкой:

auto& get_errors(std::integral_constant<char, 0>) {
    return errors1;
}

auto& get_errors(std::integral_constant<char, 1>) {
    return errors2;
}

auto& get_errors(...) {
    return errorsN;
}

и

const auto& errors = get_errors(std::integral_constant<char, machine_id>{});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...