C ++ varadi c шаблоны, смешивающие значения, массивы и объекты - PullRequest
0 голосов
/ 24 апреля 2020

Я хотел бы инициализировать класс со смесью базовых c типов, массивов и подструктур, таких как

A a { 1,                   // int
      1.2,                 // double
      "Hello",             // const char *
      { 1, 2, 3 },         // array<int>
      { 1.2, 2.4 }         // array<double>
      { 1, 1.2, "Hello" }  // sub-structure of any types
};

, где подструктура должна содержать вложенные подструктуры как хорошо.

Для базовых c типов я могу сделать это с помощью шаблонов variadi c:

#include <iostream>

class A {
public:

   template <class T, class... Args>
   void print(T v) {
      std::cout << v << std::endl;
   }

   template <class T, class... Args>
   void print(T v, Args... args) {
      std::cout << v << std::endl;
      print(args...);
   };

   template <class T, class... Args>
   A(T v, Args... args) {
      print(v, args...);
   };
};

int main() {

   A a { 1,
         1.2,
         "Hello",
   };
}

, которые правильно выдают

1
1.2
Hello

Но я терплю неудачу для массивов и подструктур. Я ограничен C ++ 11. Любая помощь высоко ценится.

Ответы [ 4 ]

2 голосов
/ 24 апреля 2020

{/*..*/} не имеет типа, и поэтому не может быть выведен в шаблоне, за исключением std::initializer_list<T> или C -array T(&)[N].

То же самое для шаблона c variadi, поскольку вы не можете предоставить все эти комбинации перегрузок вы должны предоставить типу, и, поскольку вы используете конструктор шаблона, только в самом типе:

A a { 1,                   // int
      1.2,                 // double
      "Hello",             // const char *
      std::array<int, 3>{1, 2, 3 },         // array<int>
      std::array<double, 2>{ 1.2, 2.4 }     // array<double>
      MyClass{ 1, 1.2, "Hello" }  // sub-structure of any types
};

C ++ 17 с CTAD позволяет std::array{1, 2, 3} вместо std::array<int, 3>{1, 2, 3}.

0 голосов
/ 24 апреля 2020

Я публикую это как ответ, так как мне нужно форматирование кода. Я полностью понимаю, что способ, которым я хочу сделать это, не работает. Я бы не начал, если бы не видел это раньше:

#include <iostream>

#include <json.hpp>
using json = nlohmann::json;

int main() {

   json j {
           { "Float", 1.2},
           { "Int", 2},
           { "List", { 1,2,3 }},
           { "Object", {
              { "First", 3 },
              { "Name", "Joe"}
           }}
   };

   std::cout << j << std::endl;
}

Это из https://github.com/nlohmann/json и использовались шаблоны varadi c. Он хорошо производит такой вывод:

{"Float":1.2,"Int":2,"List":[1,2,3],"Object":{"First":3,"Name":"Joe"}}

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

0 голосов
/ 24 апреля 2020

Это не вписывается в комментарий, поэтому я публикую его как ответ. То, что вы называете основной структурой , вовсе не является структурой (или типом), а формой при инициализации. А именно список инициализации . Значения между фигурными скобками сопоставляются с вашим конструктором, и, поскольку у вас есть конструктор с аргументами шаблона variadi c, ваш код компилируется. Другая причина в том, что вы предоставляете только литералы, а литералы имеют очень хорошо определенные типы: int, const char[N], float, .... Компилятор уже знает эти типы без необходимости указывать тип.

Теперь добавьте еще один уровень фигурных скобок, например:

{ 1, 2, { 3, 4, 5 }, "test" }

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

Ctor(int, int, <unknown>, const char*)

Как вы можете видеть, компилятор не знает, какой тип {3, 4, 5}, поэтому он пытается найти конструктор, который хотя бы соответствует всем другие аргументы. Если он находит его, он пытается передать часть <unknown> тому типу, который соответствует соответствующей перегрузке в этой позиции в списке параметров. Затем это вызовет другую инициализацию списка для этого указанного c параметра, и весь процесс повторяется.

Давайте возьмем наш примерный список сверху

{ 1, 2, { 3, 4, 5 }, "test" }

и набор конструкторов

Ctor(int, float*, int, const char*);             // A
Ctor(int, int, int, const char*);                // B
Ctor(int, int, std::array<int, 3>, const char*); // C

Теперь компилятор пытается сопоставить все известные типы с мнимой сигнатурой, которую мы определили выше (да const char* не является действительным типом литерала, но здесь мы его игнорируем):

Ctor(int, int, <unknown>, const char*)

Опция A немедленно исключается, поскольку типы 2-го аргумента не совпадают, поэтому у нас остаются B и C. Для B и C обе подписи соответствуют известным типам, поэтому нам осталось выяснить, что может быть <unknown>. B предлагает нам int и C std::array<int, 3>. Давайте посмотрим B: можем ли мы сделать int x{3, 4, 5} (это упрощение и только для иллюстрации)? Оказывается, C ++ не позволяет нам это делать, поэтому мы отбрасываем B как опцию. Далее C: мы можем сделать std::array<int, 3> x{3, 4, 5}. Это выглядит правильно, C ++ позволяет нам это делать. Мы нашли возможное совпадение для <unknown>, и компилятор выбирает C в качестве конструктора, который будет вызван. Отлично!

Теперь давайте объявим конструктор шаблона variadi c

template <typename ...Args>
Ctor(Args&&... args);

и сопоставим его с нашим списком. Поскольку у нас есть параметры шаблона, мы в основном говорим компилятору: берите тот тип, который нам дает аргумент. Итак, мы делаем это. Первые два и последние аргументы просты, они просто литералы, поэтому у нас есть

template <typename Arg3>
Ctor(int, int, Arg3&& arg3, const char*);

Теперь мы попробуем наш вложенный список. Аргументом является <unknown>, потому что компилятор не может понять, что такое {3, 4, 5} (мы оставляем std::initializer_list здесь как особый случай). Параметр шаблона говорит: возьмите любой тип этого аргумента, а тип аргумента - <unknown>. Вы замечаете проблему?

0 голосов
/ 24 апреля 2020

С cppreference :

Список фигурных скобок не является выражением и поэтому имеет без типа , например, decltype ({1,2 }) плохо сформирован. Отсутствие типа подразумевает, что дедукция типа шаблона не может вывести тип, который совпадает со списком фигурных скобок, поэтому, учитывая шаблон объявления void f (T); выражение f ({1,2,3}) неправильно сформировано.

Итак, как уже упоминал @ Jarod42, вам нужно явно указать тип. Вы также можете использовать переменную auto заранее, чтобы определить тип (например, в std::initializer_list<int>), а затем передать переменную в конструктор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...