Должен ли я использовать() или {} при пересылке аргументов?
У меня есть следующий класс:
struct foo
{
std::size_t _size;
int* data;
public:
explicit foo(std::size_t s) : _size(s) { }
foo(std::size_t s, int v)
: _size(s)
{
data = new int[_size];
std::fill(&data[0], &data[0] + _size, v);
}
foo(std::initializer_list<int> d)
: _size(d.size())
{
data = new int[_size];
std::copy(d.begin(), d.end(), &data[0]);
}
~foo() { delete[] data; }
std::size_t size() const { return _size; }
};
И я хочу передать ему аргументы следующим образом:
template <typename... Args>
auto forward_args(Args&&... args)
{
return foo{std::forward<Args>(args)...}.size();
//--------^---------------------------^
}
std::cout << forward_args(1, 2) << " " << forward_args(1) << " "
<< forward_args(2) << "\n";
Если я заменю {}
на ()
, вывод будет 1 1 2
вместо 2 1 1
.
Что будет иметь смысл для моего класса?
Ответы
Ответ 1
Использование {}
vs. ()
определяет, какой конструктор вызывается.
-
{}
вызовет форму foo(std::initializer_list<int> d)
, и вы получите ожидаемые результаты.
-
()
вызовет explicit foo(std::size_t s)
и foo(std::size_t s, int v)
, и во всех случаях первый элемент будет размером, поэтому, учитывая аргументы, вы получите результаты, которые вы видите.
Какую форму в пользу зависит от того, какую семантику вы хотите использовать для поддержки метода forward_args
. Если вы хотите передать параметры "как есть", тогда следует использовать ()
(в этом случае пользователю необходимо указать initialiser_list
в качестве аргумента для начала).
Возможно (возможно) форма , которую вы хотите одобрить, ()
и используется следующим образом;
template <typename... Args>
auto forward_args(Args&&... args)
{
return foo(std::forward<Args>(args)...).size();
//--------^---------------------------^
}
int main()
{
std::cout << forward_args(std::initializer_list<int>{1, 2}) << " "
<< forward_args(std::initializer_list<int>{3}) << " "
<< forward_args(std::initializer_list<int>{4}) << "\n";
}
Боковое примечание; страница cppreference на initializer_list
имеет хороший пример этого поведения.
Если требуется (т.е. поддерживать forward_args({1,2})
), , вы можете обеспечить перегрузку для initializer_list
;
template <class Arg>
auto forward_args(std::initializer_list<Arg> arg)
{
return foo(std::move(arg)).size();
}
Как отмечено: с учетом initializer_list
конструкторы классов в конечном счете являются источником путаницы, и это проявляется во время построения объекта. Существует разница между foo(1,2)
и foo{1,2}
; последний вызывает форму initializer_list
конструктора. Один из способов решения этой проблемы - использовать "тег" для дифференциации "нормальных" форм конструктора из формы initializer_list
.
Ответ 2
В этом случае, поскольку класс имеет конструктор, который принимает std::initializer_list
, использование {}
в функции factory часто изменяет семантическое значение любого списка аргументов, через которое вы проходите, путем превращения любой комбинации аргументов длиннее 2 в список initializer_list.
Это будет удивительно для пользователей этой функции.
Поэтому используйте форму ()
. Пользователи, которые хотят передать файл initializer_list, могут так явно, вызывая
forward_args({ ... });
NB для приведенного выше синтаксиса для работы вам нужно будет предоставить дополнительную перегрузку:
template <class T>
auto forward_args(std::initializer_list<T> li)
{
return foo(li).size();
}
Ответ 3
Действительно, вы не должны определять свой класс foo
как это (при условии, что он находится под вашим контролем). Два конструктора имеют перекрытие и приводят к плохой ситуации, когда две инициализации:
foo f1(3);
foo f2{3};
Значит две разные вещи; и люди, которым необходимо написать функции пересылки, сталкиваются с неразрешимыми проблемами. Это подробно описано в этом сообщении в блоге. Также std::vector
страдает от одной и той же проблемы.
Вместо этого я предлагаю использовать конструктор с тегами:
struct with_size {}; // just a tag
struct foo
{
explicit foo(with_size, std::size_t s);
explicit foo(with_size, std::size_t s, int v);
foo(std::initializer_list<int> d);
// ...
}
Теперь у вас нет перекрытия, и вы можете безопасно использовать вариант {}.