Как передать значения кортежа инициализатору члена?

Мне нужно переслать значения кортежа инициализатору члена:

struct Struct {
  Member1 member1;
  Member2 member2;

  template<typename Tuple1, typename Tuple2>
    Struct( Tuple1&& tuple1, Tuple2&& tuple2 )
      : member1(tuple1...), member2(tuple2...)
    {}
};

Приведенный выше код явно недействителен. Как я могу это выразить?

Member1 и Member2 не имеют конструктора по умолчанию / копирования / перемещения.

Я знаю о std::apply, как это предлагается в Как сделать Я разворачиваю кортеж в аргументы функции шаблона с переменным числом аргументов?. Я также знаю о std::make_from_tuple. Но я бы не знал, как использовать что-либо из этого в инициализаторе члена.

Подойдет любой стандарт C ++ (желательно C ++ 17, но подойдет и C ++ 20).

Чтобы уточнить, моя настоящая цель - создать Struct, передав ему два набора переменных аргументов, чтобы перенаправить их для инициализации member1 и member2. Я подумал, что сгруппировать два набора в кортежи могло быть хорошей идеей, поскольку это то, что делает std::map::emplace. Другие подходы также будут работать (например, передача специального объекта между двумя наборами вариативных аргументов).


person Helloer    schedule 24.12.2020    source источник


Ответы (2)


std::make_from_tuple действительно правильный выбор:

#include <tuple>
struct Member1 {
    Member1(int x,float y, char z){}

    Member1(const Member1& other)=delete;
    Member1(Member1&& other)=delete;
};

struct Member2 {
    Member2(int x,float y, char z){}

    Member2(const Member2& other)=delete;
    Member2(Member2&& other)=delete;
};

struct Struct {
  Member1 member1;
  Member2 member2;

  template<typename Tuple1, typename Tuple2>
    Struct(Tuple1&& tuple1, Tuple2&& tuple2)
      : member1(std::make_from_tuple<Member1>(std::forward<Tuple1>(tuple1))),
       member2(std::make_from_tuple<Member2>(std::forward<Tuple2>(tuple2)))
    {}
};

int main(){
    Struct c(std::tuple{1,1.1,'c'},std::tuple{2,2.2,'x'});
}

Демо Godbolt.

person Quimby    schedule 24.12.2020
comment
Ох, это выглядит очень просто! Я предположил, что передача результата std::make_from_tuple в инициализатор вызовет вызов конструктора копирования! Боюсь, что я ошибочно поместил туда std::move, что вынудило компилятор использовать удаленный конструктор перемещения. - person Helloer; 25.12.2020
comment
Кстати, пример не построен с использованием GCC. Это сработает, если я использую std::make_tuple вместо инициализации std::tuple списком инициализаторов. Я подозреваю, что это ошибка GCC. - person Helloer; 25.12.2020
comment
Это приведет к перемещению конструктора до C ++ 17 без оптимизации исключения копирования, поскольку std::make_from_tuple возвращает rvalue. Но после копирования нет ни перемещения, ни копирования, объект создается на месте. Да, перемещение на самом деле плохо, потому что в этом случае оно отключает элицию. - person Quimby; 25.12.2020
comment
@Helloer Это странно, не уверен, что это вызывает, здесь должны сработать вычеты шаблонов для кортежей. - person Quimby; 25.12.2020
comment
@Helloer Могу я задать вопрос об этом, или вы хотите? Меня интересует причина этой ошибки. - person Quimby; 25.12.2020
comment
@Helloer Я обновил ответ, кортежи следует пересылать, а не перемещать, поскольку они являются универсальными ссылками. - person Quimby; 25.12.2020
comment
Извините, до сих пор я был AFK. Я вижу сейчас. Это имеет смысл. Ошибка GCC была загадочной. Я тоже был в порядке с использованием std::make_tuple. - person Helloer; 25.12.2020

Однако cppreference.com содержит хороший пример реализации std::make_from_tuple, как вы обнаружили, вы не можете использовать его из-за отсутствия конструктора копирования базового класса.

Однако его хлебные крошки позволяют адаптировать его для обхода этих ограничений:

#include <tuple>
#include <iostream>

struct Member1 {
    Member1(int a, int b)
    {
        std::cout << a << " "
              << b
              << std::endl;
    }

    Member1(const Member1 &)=delete;
    Member1(Member1 &&)=delete;
};

struct Member2 {
    Member2(const char *str)
    {
        std::cout << str << std::endl;
    }

    Member2(const Member2 &)=delete;
    Member2(Member2 &&)=delete;
};

// De-obfucation shortcut

template<typename Tuple>
using make_index_sequence_helper=std::make_index_sequence
    <std::tuple_size_v<std::remove_reference_t<Tuple>>>;

struct Struct {
    Member1 member1;
    Member2 member2;

    template<typename Tuple1, typename Tuple2>
    Struct( Tuple1&& tuple1,
        Tuple2&& tuple2 )
        : Struct{std::forward<Tuple1>(tuple1),
        make_index_sequence_helper<Tuple1>{},
        std::forward<Tuple2>(tuple2),
        make_index_sequence_helper<Tuple2>{}}
    {
    }

    template<typename Tuple1, std::size_t ...tuple1_args,
         typename Tuple2, std::size_t ...tuple2_args>
    Struct(Tuple1 && tuple1,
           std::index_sequence<tuple1_args...>,
           Tuple2 && tuple2,
           std::index_sequence<tuple2_args...>)
        : member1{std::get<tuple1_args>(tuple1)...},
          member2{std::get<tuple2_args>(tuple2)...}
    {
    }
};

int main()
{
    Struct s{ std::tuple<int, int>{2, 3},
        std::tuple<const char *>{"Hello world"}};

    return 0;
}

Протестировано с gcc 10 с -std=c++17.

person Sam Varshavchik    schedule 24.12.2020
comment
Ох, спасибо! Я проверял cppreference несколько раз, но не мог понять трюк с последовательностью индекса. По какой-то причине мой мозг решил, что этот пример расширил значения до вспомогательной функции с чем-то похожим на std::apply, и отказался понять истину. - person Helloer; 25.12.2020
comment
std::make_from_tuple не требует ни копировать, ни перемещать ctors из-за исключения копирования, поэтому его можно использовать нормально. - person Quimby; 25.12.2020
comment
Я приму ответ Куимби, так как он проще и я буду использовать его. Но я нашел этот ответ очень полезным и информативным! - person Helloer; 25.12.2020