У меня есть простая структура шаблона, связывающая строку со значением
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*>>
, но это кажется плохим решением … Есть ли лучший способ сделать это?
Пара вещей …
C ++ 11 (который у вас, похоже, есть, так как вы говорите о std::initializer_list
) имеет типизированные переменные аргументы, в частности они названы вариационные шаблоны
Шаблоны Java и шаблоны C ++ — совершенно разные звери. Обобщения Java создают единственный тип, который хранит ссылку на Object
и обеспечивает автоматическое приведение и вывод типов в интерфейсе, но важным моментом является то, что он выполняет стирание типов.
Я бы порекомендовал вам объяснить проблему, которую вы хотите решить, и получить предложения по ее решению, которые являются идиоматическими в C ++. Если вы хотите по-настоящему имитировать поведение в Java (которое, я не могу настаивать на том, что это другой язык и имеет другие идиомы), вы можете использовать стирание типов в C ++ вручную (т.е. использовать boost::any
). Но я очень редко чувствую необходимость полного стирания типа в программе … используя вариант типа (boost::variant
) немного чаще встречается.
Если ваш компилятор поддерживает шаблоны с переменным числом аргументов (не все компиляторы поддерживают), вы всегда можете поиграть с этим, но припрятать Поля для последующего вектора могут быть немного сложными для полностью общего подхода, если вы не используете стирание типа. (Опять же, какую проблему решить? Могут быть более простые решения …)
Обобщения 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, а затем выполняла этот код таким образом, чтобы отладчик мог подключиться обратно к коду, который пишет код в качестве источника. из ошибок.
Нет необходимости использовать подстановочные знаки в шаблонах 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...);
}
Это не очень идиоматично для C ++. Это может быть сделано, возможно; Книга Коплина может иметь некоторые идеи. Но C ++ строго типизирован, потому что верит в типизацию; попытка превратить его в Smalltalk или сложить, как фазан, может привести к слезам.