вторник, 2 декабря 2025 г.

[prog.c++] Интересно, а какой код понятнее?

В современном C++ одни и те же вещи можно сделать по разному.

Например, у нас есть список типов, для каждого из которых нужно сделать какое-то действие. Что-то вроде for_each-а, но для списка типов.

Можно сделать это вот так:

template<typename... Types>
void for_each_type_via_lambda(int arg) {
    const auto action = [arg]<typename T>() {
        std::cout << typeid(T).name() << " - " << arg << std::endl;
    };
    (action.template operator()<Types>(), ...);
}

Здесь используется шаблон лямбда функции, для использования которого нам приходится явно указывать как его вызывать.

А можно сделать более старым способом, без лямбды, но с помощью вспомогательной функции:

template<typename T>
void do_something(int arg) {
    std::cout << typeid(T).name() << " - " << arg << std::endl;
}

template<typename... Types>
void for_each_type(int arg) {
    (do_something<Types>(arg), ...);
}

Результат будет один и тот же. Но вот понятность двух этих вариантов лично для меня совершенно разная.

Интересно, а какой из вариантов более понятен для вас?

PS. Этот пример на wandbox "для поиграться".

8 комментариев:

  1. Локальные лямбды чаще раздражают, но если файл с кодом сильно распухает, то чем локальнее контекст, тем меньше контекстной информации потребуется держать в уме. В итоге прошел все 5 стадий принятия первого варианта исполнения

    ОтветитьУдалить
  2. Наверное этот

    template
    void print_names_of_types(int arg) {
    ((std::cout << typeid(Types).name() << " - " << arg << std::endl), ...);
    }

    и да, он работает, я проверил в wandbox

    ОтветитьУдалить
    Ответы
    1. В реальном коде от каждого типа в Types нужно было не typeid брать ;)
      typeid использовался только для примера.

      Удалить
  3. @eao197 Но это не "магия" typeid. Так тоже работает, например

    template
    auto get_name_of_type() {
    return typeid(T).name();
    }

    template
    void print_names_of_types(int arg) {
    ((std::cout << get_name_of_type() << " - " << arg << std::endl), ...);
    }

    ОтветитьУдалить
    Ответы
    1. Я так понимаю, что ваш код с get_name_of_type является эквивалентом вот этого: https://wandbox.org/permlink/yDe10p40BNfQSvdn
      Ну так это и есть один из вариантов, показанных в посте.

      Удалить
  4. @еао197 Я просто хотел показать, что typeid здесь нипричём. А вообще, по-моему, на вопрос поста я ответил. Чем меньше "сущностей" в коде, тем понятнее, я считаю ;)

    ОтветитьУдалить
    Ответы
    1. > Чем меньше "сущностей" в коде, тем понятнее, я считаю ;)

      Меньше сущностей получилось только за счет того, что вы в своем однострочнике сделали единственную операцию для типа T. Представьте, что она будет не одна, а для каждого типа нужно сделать 5-10 операций.

      Удалить
  5. @еао197
    Ладно, я не понял, чего вы на самом деле добиваетесь, хотя в рамках поста я ответил верно, я считаю ;)
    Вот вам, просто шутки ради, вариант с несколькими операциями

    template
    auto get_name_of_type() {
    return typeid(T).name();
    }

    template
    int type_to_int();

    template <>
    int type_to_int () {
    return 1;
    }

    template <>
    int type_to_int () {
    return 2;
    }

    template <>
    int type_to_int () {
    return 3;
    }

    template
    void print_names_of_types(int arg) {
    ((std::cout << get_name_of_type (),
    std::cout << ":",
    std::cout << (type_to_int () + arg),
    std::cout << std::endl),
    ...);
    }

    ОтветитьУдалить