Предположим, у меня есть структура, которая позже будет использоваться в векторе. Я хочу добавить элемент в вектор, используя emplace_back, не создавая временных элементов. Я ожидал, что смогу использовать std::forward_as_tuple для передачи двух значений в качестве параметров для Foo. Однако он не может скомпилироваться.
Как правильно это сделать?
#include <utility>
#include <vector>
struct Foo {
int a;
float b;
Foo(int a, float b) : a(a), b(b) {}
};
std::vector<tuple<int, int, Foo>> vec;
vec.emplace_back(0, 1, std::forward_as_tuple(1, 2.0f));
Конечно, я мог бы сделать это:
vec.emplace_back(0, 1, Foo(1, 2.0f));
Но это создает временный Foo, который в первую очередь противоречит цели использования emplace_back.
Можете ли вы изменить Foo, чтобы конструктор принимал std::tuple?
Foo легко скопировать, поэтому нет смысла беспокоиться о стоимости создания временного Foo. Этот вопрос, вероятно, следует закрыть как обман, связанный со стоимостью тривиально копируемых типов, если только вы не сможете придумать осмысленный пример.
В противном случае вы можете создать тип, подобный кортежу, с оператором преобразования шаблона.
@JanSchultke Foo — это просто заполнитель для более простого примера. Фактический вариант использования — это класс, который невозможно скопировать тривиально.





Один из способов сделать это — добавить в Foo конструктор кортежа.
struct Foo {
int a;
float b;
Foo(int a, float b) : a(a), b(b) {}
Foo(std::tuple<int, float> tup) : a(get<0>(tup)), b(get<1>(tup)) {}
};
Если вы использовали std::pair, вы могли бы использовать конструктор std::piecewise_construct_t, однако его вариационная версия не существует.
std::vector<std::pair<int, Foo>> vec;
vec.emplace_back(std::piecewise_construct, std::tuple(1), std::forward_as_tuple(1, 2.0f));
@ 463035818_is_not_an_ai да, вы можете добавить комбинации кортежей ссылок в набор перегрузки, в зависимости от того, что делает Foo тяжелым
ок, тогда мое понимание было в порядке. Вкратце (длинный комментарий удалю): Вы обмениваете постройку временного Foo на постройку временного std::tuple<int,float>.
Даже если Foo не является тривиально копируемым, std::vector требует, чтобы Foo был как минимум подвижным. Сейчас вы пытаетесь сэкономить один дополнительный ход, который не нужен почти для каждого типа.
Но мы занимаемся взломом языка, поэтому вы можете использовать приведения, чтобы сохранить этот ход.
template<typename F>
struct initializer
{
F f;
template<typename T>
operator T() &&
{
return std::forward<F>(f)(std::type_identity<T>{});
}
};
template<typename F>
initializer(F&&) -> initializer<F>;
template<typename... Args>
auto initpack(Args&&... args)
{
return initializer{[&](auto t) {
using Ret = typename decltype(t)::type;
return Ret{std::forward<Args>(args)...};
}};
}
int main()
{
std::vector<std::tuple<int, int, Foo>> vec;
vec.emplace_back(0, 1, initpack(2, 3.f));
}
Почему вы считаете это взломом языка? Вы имеете в виду, что в большинстве случаев vec.emplace_back(0, 1, Foo(1, 2.0f)) должно подойти?
@YkovDan Это загадочно и запутанно. Да, за исключением некоторых чрезвычайно странных типов, ходов должно быть достаточно. Вероятно, виноваты исключительные типы, и их в любом случае следует переписать.
Примечание: предлагаемое вами решение не полностью противоречит цели
emplace_back: вы сохраняете созданиеtupleвременного. Но я понимаю, что в идеале вам следует избегать иFoo.