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

Кортеж вектор и initializer_list

Я попытался скомпилировать следующие фрагменты с gcc4.7

vector<pair<int,char> > vp = {{1,'a'},{2,'b'}};
//For pair vector, it works like a charm.

vector<tuple<int,double,char> > vt = {{1,0.1,'a'},{2,4.2,'b'}};

Однако для вектора кортежей компилятор жалуется:

Ошибка: преобразование в 'std:: tuple из списка инициализаторов будет использовать явный конструктор' constexpr std:: tuple < > :: tuple (_UEements & &...) [с _UElements = {int, double, char}; = void; _Elements = {int, double, char}]

Информация об ошибке, которую проливает компилятор, является для меня полной тарабарщиной, и я понятия не имею, как были реализованы конструкторы кортежа, но я знаю, что они полностью в порядке с единой инициализацией (например: tuple<int,float,char>{1,2.2,'X'}), поэтому, Мне интересно, является ли проблема, с которой я столкнулся, только TODO компилятора или что-то определенное стандартом С++ 11.

Любой ответ будет оценен!

4b9b3361

Ответ 1

Если вы указали справочную страницу в конструкторе tuple, вы увидите, что он не принимает std::initializer_list, который необходим, чтобы класс мог принимать списки инициализаторов.

Вы инициализируете вектор std::make_tuple:

vector<tuple<int,double,char>> vt =
    { std::make_tuple(1,0.1,'a'), std::make_tuple(2,4.2,'b') };

Ответ 2

Соответствующими конструкторами std::tuple являются explicit. Это означает, что то, что вы хотите сделать, невозможно, поскольку синтаксис, который вы хотите использовать, определяется с точки зрения инициализации копии (что запрещает вызов конструктора explicit). Напротив, std::tuple<int, float, char> { 1, 2.2, 'X' } использует прямую инициализацию. std::pair имеет конструкторы не explicit.

Либо используйте прямую инициализацию, либо одну из стандартной функции factory (например, std::make_tuple).

Ответ 3

Это действительно выполнимо, с возможностями С++ 11.

Да, initializer_list хочет, чтобы все его элементы были одного типа. Хитрость заключается в том, что мы можем создать класс-оболочку, который может быть static_cast для всех типов, которые мы хотим. Этого легко достичь:

 template <typename... tlist>
 class MultiTypeWrapper {
 };

 template <typename H>
 class MultiTypeWrapper<H> {
 public:
   MultiTypeWrapper() {}

   MultiTypeWrapper(const H &value) : value_(value) {}

   operator H () const {
     return value_;
   }
 private:
   H value_;
 };

 template <typename H, typename... T>
 class MultiTypeWrapper<H, T...> 
   : public MultiTypeWrapper<T...> {

 public:
   MultiTypeWrapper() {}

   MultiTypeWrapper(const H &value) : value_(value) {}

   // If the current constructor does not match the type, pass to its ancestor.
   template <typename C>
   MultiTypeWrapper(const C &value) : MultiTypeWrapper<T...>(value) {}

   operator H () const {
     return value_;
   }
 private:
   H value_;
 };

С помощью неявных конструкторов преобразования мы можем передать что-то вроде {1,2.5, 'c', 4} в initializer_list (или вектор, который неявно преобразует initializer_list) типа MultiTypeWrapper. Это означает, что мы не можем написать такую ​​функцию, как ниже, чтобы принять такой intializer_list как аргумент:

template <typename... T>
std::tuple<T...> create_tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  ....
}

Мы используем еще один трюк, чтобы отбросить каждое значение в векторе до его исходного типа (обратите внимание, что мы предоставляем неявное преобразование в определении MultiTypeWrapper) и присваиваем его соответствующему слоту в кортеже. Это похоже на рекурсию по аргументам шаблона:

template <int ind, typename... T>
class helper {
public:
  static void set_tuple(std::tuple<T...> &t, const std::vector<MultiTypeWrapper<T...> >& v) {
    std::get<ind>(t) = static_cast<typename std::tuple_element<ind,std::tuple<T...> >::type>(v[ind]);
    helper<(ind-1),T...>::set_tuple(t,v);
  }
};



template <typename... T>
class helper<0, T...> {
public:
  static void set_tuple(std::tuple<T...> &t, const std::vector<MultiTypeWrapper<T...> >& v) {
    std::get<0>(t) = static_cast<typename std::tuple_element<0,std::tuple<T...> >::type>(v[0]);
  }
};



template <typename... T>
std::tuple<T...> create_tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  std::tuple<T...> res;
  helper<sizeof...(T)-1, T...>::set_tuple(res, init);
  return res;
}

Обратите внимание, что мы должны создать вспомогательный класс для set_tuple, поскольку С++ не поддерживает специализацию функции. Теперь, если мы хотим протестировать код:

auto t = create_tuple<int,double,std::string>({1,2.5,std::string("ABC")});
printf("%d %.2lf %s\n", std::get<0>(t), std::get<1>(t), std::get<2>(t).c_str());

Вывод будет:

1 2.50 ABC

Это проверено на моем рабочем столе с clang 3.2

Надеюсь, мой вклад поможет:)