Подтвердить что ты не робот

Зачем использовать переменные аргументы сейчас, когда доступны списки инициализаторов?

Мне было интересно, каковы преимущества вариационных аргументов над списками инициализаторов. Оба имеют одинаковую способность - передавать неопределенное количество аргументов функции.

Я лично считаю, что списки инициализаторов немного более элегантны. Синтаксис менее неудобен.

Кроме того, похоже, что списки инициализаторов имеют значительно лучшую производительность по мере увеличения количества аргументов.

Итак, что мне не хватает, кроме возможности использовать также переменные аргументы в C?

4b9b3361

Ответ 1

Если переменными аргументами вы понимаете эллипсы (как в void foo(...)), то они становятся более или менее устаревшими variadic templates, а не списками инициализаторов - все еще могут быть некоторые варианты использования для эллипсов при работе с SFINAE для реализации (например) черт типа или для совместимости с C, но я расскажу об обычных случаях использования здесь.

Вариадические шаблоны, фактически, допускают разные типы для пакета аргументов (фактически, любого типа), в то время как значения списков инициализаторов должны быть конвертированы в базовый тип списка инициализаторов (и сужение конверсий не допускается)

#include <utility>

template<typename... Ts>
void foo(Ts...) { }

template<typename T>
void bar(std::initializer_list<T>) { }

int main()
{
    foo("Hello World!", 3.14, 42); // OK
    bar({"Hello World!", 3.14, 42}); // ERROR! Cannot deduce T
}

Из-за этого списки инициализаторов реже используются, когда требуется дедукция типа, если тип аргументов действительно не является однородным. С другой стороны, шаблоны Variadic предоставляют версию типа списка вариационных аргументов эллипсов.

Кроме того, вызов функции, которая принимает список инициализаторов, требует включения аргументов в пару фигурных скобок, что не относится к функции, принимающей пакет вариационных аргументов.

Наконец (ну, есть и другие отличия, но это те, которые более релевантны вашему вопросу), значения в списках инициализаторов являются объектами const. В абзаце 18.9/1 стандарта С++ 11:

Объект типа initializer_list<E> обеспечивает доступ к массиву объектов типа const E. [...] Копирование списка инициализаторов не копировать базовые элементы. [...]

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

В общем, во всяком случае, при использовании объекта в качестве элемента списка инициализаторов мы либо сделаем его копию (если это lvalue), либо удалим от него (если это rvalue):

#include <utility>
#include <iostream>

struct X
{
    X() { }
    X(X const &x) { std::cout << "X(const&)" << std::endl; }
    X(X&&) { std::cout << "X(X&&)" << std::endl; }
};

void foo(std::initializer_list<X> const& l) { }

int main()
{
    X x, y, z, w;
    foo({x, y, z, std::move(w)}); // Will print "X(X const&)" three times
                                  // and "X(X&&)" once
}

Другими словами, списки инициализаторов не могут использоваться для передачи аргументов по ссылке (*), не говоря уже о совершенной пересылке:

template<typename... Ts>
void bar(Ts&&... args)
{
    std::cout << "bar(Ts&&...)" << std::endl;
    // Possibly do perfect forwarding here and pass the
    // arguments to another function...
}

int main()
{
    X x, y, z, w;
    bar(x, y, z, std::move(w)); // Will only print "bar(Ts&&...)"
}

(*) Следует, однако, отметить, что списки инициализаторов (в отличие от всех других контейнеров стандартной библиотеки С++) имеют ссылочную семантику, поэтому, хотя копия/перемещение элементов выполняется при вставке элементов в список инициализаторов, копирование самого списка инициализатора не приведет к копированию/перемещению содержащихся объектов (как указано в параграфе приведенного выше стандарта):

int main()
{
    X x, y, z, w;
    auto l1 = {x, y, z, std::move(w)}; // Will print "X(X const&)" three times
                                       // and "X(X&&)" once

    auto l2 = l1; // Will print nothing
}

Ответ 2

Вкратце, вариативные функции C-стиля производят меньше кода при компиляции, чем шаблоны с вариациями в стиле С++, поэтому, если вас беспокоит бинарный размер или давление в кеше инструкций, вам следует рассмотреть возможность реализации ваших функций с помощью varargs вместо шаблона.

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