Виртуальный метод шаблона С++
У меня есть абстрактный класс (я знаю, что он не будет компилироваться таким образом, но для понимания того, что я хочу делать):
class AbstractComputation {
public:
template <class T> virtual void setData(std::string id, T data);
template <class T> virtual T getData(std::string id);
};
class Computation : public AbstractComputation {
public:
template <class T> void setData(std::string id, T data);
template <class T> T getData(std::string id, T data);
};
Поэтому, когда я вызываю setData<double>("foodouble", data)
, я хочу, чтобы двойник, идентифицированный foodouble
(внутренний механизм, который не является главной проблемой здесь), должен быть установлен на двойные данные.
Итак, как это сделать?
Я думаю, что может быть среднее значение, набрав что-то вроде virtual void setData<double>(std::string id, double data)
, но я не знаю, как это сделать.
Ответы
Ответ 1
Проблема заключается в том, что вы не можете легко смешивать статический полиморфизм времени (шаблоны) с полиморфизмом во время выполнения. Причина того, что язык, запрещающий конкретную конструкцию в вашем примере, заключается в том, что существуют потенциально бесконечные различные типы, которые могут быть созданы для создания функции члена вашего шаблона, а это, в свою очередь, означает, что компилятор должен будет генерировать код для динамического отправки тех многих типов, которые недопустимо.
Существуют разные вещи, которые можно сделать здесь, чтобы обойти ограничение, в основном либо убрать статический, либо динамический полиморфизм. Удаление динамического полиморфизма из уравнения может быть выполнено путем предоставления типа, из которого не производятся, для хранения отображений <key,value>
, а затем предлагает шаблон, который разрешает это только на базовом уровне:
class AbstractComputation {
public:
template <typename T>
void setData( std::string const & id, T value ) {
m_store.setData( id, value );
}
template <typename T>
T getData( std::string const & id ) const {
return m_store.getData<T>( id );
}
protected:
ValueStore m_store;
};
Теперь выведение классов может получить доступ к ValueStore
из базы и не требуется полиморфизм. (Это также может быть сделано путем реализации функций непосредственно в AbstractComputation
, но, вероятно, имеет смысл разделить проблемы)
Другой вариант - поддерживать полиморфизм во время выполнения, но удалять статический полиморфизм. Это можно сделать, выполнив стирание типа в базовом классе, а затем отправив соответствующую функцию (не templated), которая принимает аргументы с стиранием типа. Простейшая версия этого просто использует boost::any
:
class AbstractComputation {
public:
template <typename T>
void setData( std::string const & id, T value ) {
setDataImpl( id, boost::any( value ) );
}
template <typename T>
T getData( std::string const & id ) const {
boost::any res = getDataImpl( id );
return boost::any_cast<T>( res );
}
protected:
virtual void setDataImpl( std::string const & id, boost::any const & value ) = 0;
virtual boost::any getDataImpl( std::string const & id ) const = 0;
};
Как стирание типа реализовано под капотом, интересно, но вне сферы действия здесь важна то, что boost::any
представляет собой конкретный (не шаблонный) тип, который может хранить любой тип внутри, используя стирание типа на аргументы и в то же время допускают безопасное извлечение данных.
Ответ 2
В некоторых случаях может быть достаточно переместить шаблон с уровня метода на уровень класса, например:
#include <iostream>
template<typename T>
class AbstractComputation {
public:
virtual void setData(std::string id, T data)
{
std::cout << "base" << std::endl;
}
};
template<typename T>
class Computation : public AbstractComputation<T> {
public:
virtual void setData(std::string id, T data)
{
std::cout << "derived" << std::endl;
}
};
int main()
{
AbstractComputation<int> *x = new Computation<int>();
x->setData("1", -1);
delete x;
return 0;
}
Ответ 3
Во-первых, вы не можете иметь функции шаблона virtual
. Поскольку шаблоны разрешены во время компиляции, virtual
не будет работать, поскольку компилятор не знает, какой шаблон выбрать. См. здесь, для получения дополнительной информации об этом.
Ответ 4
Используйте boost::any
, чтобы принять нулевое значение, а затем, когда вы действительно установите, возьмите из него правильный тип.
Ответ 5
Вы можете использовать boost::any
в вашем случае.
virtual void setData(std::string id, boost::any data);
Это оболочка, которая может инкапсулировать почти все.
Дополнительная информация по аналогичной теме в этом ответе.
Ответ 6
Если вы знаете список возможных типов заранее, препроцессор может помочь:
#define MY_CLASSES MYTYPE(int) MYTYPE(float) MYTYPE(double)
class AbstractComputation {
public:
# define MYTYPE(T) virtual void setData(std::string id, T data)=0;\
virtual void getData(std::string id, T& dst_data)=0;
MY_CLASSES
# undef MYTYPE
};
class Computation : public AbstractComputation {
public:
# define MYTYPE(T) virtual void setData(std::string id, T data){std::cout<<"writing: "<<data<<std::endl;}\
virtual void getData(std::string id, T& dst_data){dst_data=0;/*put your actual implementation here*/}
MY_CLASSES
# undef MYTYPE
};
Если вы не знаете полный список возможных типов, возможно, ваша проблема неразрешима. Стирание типа, как упоминалось другими, также может помочь.. но не при любых обстоятельствах.