Шаблоны, принимающие «что угодно» в переполнении стека

У меня есть простая структура шаблона, связывающая строку со значением

template<typename T> struct Field
{
std::string name; T self;
}

У меня есть функция, которую я хочу принять 1 или более полей любого типа, и поля могут быть разных типов, поэтому я использую std::initializer_list потому что C ++, насколько мне известно, не имеет типизированных переменных аргументов, не может определить размер переменных аргументов и должен иметь по крайней мере еще один аргумент, чтобы определить, с чего начать.

Проблема в том, что я не знаю, как сказать ему принимать поля, которые могут быть разных типов. В Java я бы просто использовал foo(Field<?> bar, Field<?>... baz), но в C ++ отсутствуют как типизированные переменные аргументы, так и шаблоны. Моя единственная другая идея — сделать параметр типа
std::initializer_list<Field<void*>>, но это кажется плохим решением … Есть ли лучший способ сделать это?

7

Решение

Пара вещей …

  • C ++ 11 (который у вас, похоже, есть, так как вы говорите о std::initializer_list) имеет типизированные переменные аргументы, в частности они названы вариационные шаблоны

  • Шаблоны Java и шаблоны C ++ — совершенно разные звери. Обобщения Java создают единственный тип, который хранит ссылку на Object и обеспечивает автоматическое приведение и вывод типов в интерфейсе, но важным моментом является то, что он выполняет стирание типов.

Я бы порекомендовал вам объяснить проблему, которую вы хотите решить, и получить предложения по ее решению, которые являются идиоматическими в C ++. Если вы хотите по-настоящему имитировать поведение в Java (которое, я не могу настаивать на том, что это другой язык и имеет другие идиомы), вы можете использовать стирание типов в C ++ вручную (т.е. использовать boost::any). Но я очень редко чувствую необходимость полного стирания типа в программе … используя вариант типа (boost::variant) немного чаще встречается.

Если ваш компилятор поддерживает шаблоны с переменным числом аргументов (не все компиляторы поддерживают), вы всегда можете поиграть с этим, но припрятать Поля для последующего вектора могут быть немного сложными для полностью общего подхода, если вы не используете стирание типа. (Опять же, какую проблему решить? Могут быть более простые решения …)

7

Другие решения

Обобщения Java ближе к тому, чтобы просто boost::any в self переменная, чем в C ++ шаблонах. Дайте это попробовать. Шаблоны C ++ создают типы, которые по умолчанию не имеют времени выполнения или динамической взаимосвязи друг с другом.

Вы можете ввести такие отношения вручную, например, с помощью общего родителя и типа стирания и разумного использования pImpl и умные указатели.

Вариантные аргументы типа C вышли из моды в C ++ 11. Аргументы шаблона Variardic очень безопасны по типу, если ваш компилятор поддерживает их (поддержка CTP для MSVC 2012 в ноябре 2012 года имеет поддержку (не обновление 1, CTP), как Clang и не древние версии gcc).

Шаблоны в C ++ — это своего рода метапрограммирование, ближе к написанию программы, которая пишет программу, чем к Java Generics. Java Generic имеет одну общую «двоичную» реализацию, в то время как каждый экземпляр шаблона C ++ представляет собой совершенно другую «программу» (которая с помощью процедур, таких как свертывание COMDAT, может быть сведена к одной двоичной реализации), детали которой описываются шаблоном код.

 template<typename T>
struct Field {
T data;
};

небольшая программа, которая говорит «вот как создать типы полей». Когда вы проходите в int а также doubleКомпилятор делает что-то примерно так:

 struct Field__int__ {
int data;
};
struct Field__double__ {
double data;
};

и вы не ожидаете, что эти два типа будут конвертируемыми между.

Обобщения Java, с другой стороны, создают что-то вроде этого:

struct Field {
boost::any __data__;
template<typename T>
T __get_data() {
__data__.get<T>();
}
template<typename T>
void __set_data(T& t) {
__data__.set(t);
}
property data; // reading uses __get_data(), writing uses __set_data()
};

где boost::any является контейнером, который может содержать экземпляр любого типа и доступ к data поле перенаправляет через эти средства доступа.

C ++ предоставляет средства для написания чего-то эквивалентного дженерикам Java с использованием шаблонного метапрограммирования. Чтобы написать что-то вроде шаблонов C ++ в Java, вам нужно, чтобы ваша программа на Java выводила пользовательский байт или исходный код Java, а затем выполняла этот код таким образом, чтобы отладчик мог подключиться обратно к коду, который пишет код в качестве источника. из ошибок.

4

Нет необходимости использовать подстановочные знаки в шаблонах C ++, поскольку в C ++ он всегда знает тип и не «стирается», как в Java. Написать void foo(Field<?> bar, Field<?>... baz) метод (или функция) в C ++, вы бы написали:

 template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz);

каждый Field<Ts> может быть другого типа. Чтобы использовать переменные параметры внутри функции, вы просто используете baz..., Допустим, вы хотите вызвать другую функцию:

 template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz)
{
foo2(baz...);
}

Вы также можете расширить тип с помощью Field<Ts>..., так что если вы хотите поместить его в кортеж (вы не можете поместить их в массив, так как они могут быть разных типов):

 template<class T, class... Ts>
void foo(Field<T> bar, Field<Ts>... baz)
{
std::tuple<Field<Ts>...> data(baz...);
}
1

Это не очень идиоматично для C ++. Это может быть сделано, возможно; Книга Коплина может иметь некоторые идеи. Но C ++ строго типизирован, потому что верит в типизацию; попытка превратить его в Smalltalk или сложить, как фазан, может привести к слезам.

-1
По вопросам рекламы [email protected]