Шаблоны: создание экземпляра (и ссылка на) нетипизированного параметра во время выполнения? - PullRequest
2 голосов
/ 11 августа 2010

Я разработал универсальный класс "Unsigned" или действительно шаблон класса Unsigned<size_t N>, который моделирует после встроенных в C (C ++) неподписаний, используя в качестве параметра значение uint8_t s.Например, Unsigned<4> идентично uint32_t, а Unsigned<32> будет идентично uint256_t - если оно существует.

До сих пор мне удавалось следовать большинству, если не всей семантикеожидается от встроенного unsigned - в частности sizeof(Natural<N>)==N, (Natural<N>(-1) == "max_value_all_bits_1" == ~Natural<N>(0)), совместимость с abs (), sign (), div (с использованием пользовательской структуры div_t), ilogb () (кажется, исключая GCC) и numeric_limits<>.

Однако я сталкиваюсь с проблемой, заключающейся в том, что, поскольку 1.- шаблон класса - это просто шаблон, поэтому шаблонные формы не связаны, и 2.- нетипизированный параметр шаблона требует «compile-постоянная времени ", которая намного строже, чем" a const ", я по существу не могу создать неподписанное с неизвестным N .

Другими словами, я не могуиметь такой код:

...
( ... assuming all adequate headers are included ...)
using namespace std;
using lpp::Unsigned;
std::string str;
cout<< "Enter an arbitrarily long integer (end it with <ENTER>) :>";
getline(cin, str, '\n'); 
const int digits10 = log10(str.length()) + 1; 
const int digits256 = (digits10 + 1) * ceil(log(10)/log(256)); // from "10×10^D = 256^T"
// at this point, I "should" be able to, semantically, do this:
Unsigned<digits256> num; // <-- THIS I CAN'T -- num would be guaranteed 
                         // big enough to hold str's binary expression, 
                         // no more space is needed
Unsigned::from_str(num, str); // somehow converts (essentially a base change algo)
// now I could do whatever I wanted with num "as if" a builtin.
std::string str_b3 = change_base(num, 3); // a generic implemented somehow
cout<< "The number above, in base 3, is: "<< str_b3<< endl;
...

(A / N - это часть тестового набора для Unsigned, который читает «немного большое число» (я пробовал до 120 цифр - после установки Nсоответственно) и делает такие вещи, как выражение его в других основах, что само по себеуже проверяет все арифметические функции.)

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

Первое, что я подумал, было то, что если бы я не былв состоянии выбрать Unsigned<N> по моему выбору, я мог бы по крайней мере выбрать из набора предварительно выбранных значений N, что привело бы к вызову адекватного конструктора во время выполнения, но в зависимости от значения времени компиляции:

???? GetMeAnUnsigned (size_t S) {
  switch (S) {
    case 0: { throw something();  } // we can't have a zero-size number, right?
    case 1, 2, 3, 4: { return Unsigned<4>(); break; }
    case 5, 6, 7, 8: { return Unsigned<8>(); break; }
    case 9, 10, 11, 12, 13, 14, 15, 16: { return Unsigned<16>(); break; }
    ....
    default: { return Unsigned<128>(); break; } // wow, a 1Kib number!
    } // end switch
  exit(1); // this point *shouldn't* be reachable!
  } // end function

Мне лично этот подход нравится.Однако я не знаю, что я могу использовать, чтобы указать тип возвращаемого значения.На самом деле он не «решает» проблему, он лишь в некоторой степени снижает ее серьезность.Я уверен, что трюк с переключателем будет работать, так как экземпляры равны от константы времени компиляции, он только меняет , из которых будет иметь место.

Единственной жизнеспособной помощью для объявления возвращаемого типа является новая конструкция C ++ 0 (1?) X " decltype ", которая позволила бы мне получить адекватный тип, что-тонапример, если я правильно понял функцию:

decltype (Unsigned<N>) GetMeAnUnsigned (size_t S) {
  .. do some choices that originate an N
  return Unsigned<N>();
  }

... или что-то в этом роде.Я еще не вошел в C ++? X за пределами auto (для итераторов), поэтому первый вопрос будет таким: будут ли такие функции, как decltype или auto, помочь мне достичь того, чего я хочу? ( Runtime выбор экземпляров, даже если они ограничены)

В качестве альтернативы я подумал, что если бы проблема заключалась в отношении между моими классами, то я мог бы сделать их all"своего рода" Base, получая сам шаблон:

template <size_t N>
class Unsigned : private UnsignedCommon { ...

... но я оставил этот подход на заднем плане, потому что, ну, никто не делает , что (сделать все "своего рода") со встроенными модулями, плюс для случаев, когда один действительно фактически рассматривает их как общий класс, он требует инициализации статики, возврата указателей и оставления клиента для уничтожения, еслиЯ правильно помню.Тогда второй вопрос: я ошибся, отказавшись от этой альтернативы слишком рано?

Ответы [ 4 ]

2 голосов
/ 11 августа 2010

Короче говоря, ваша проблема ничем не отличается от встроенных целочисленных типов. Учитывая short, вы не можете хранить большие целые числа в нем. И вы не можете во время выполнения решить, какой тип целого числа использовать, если вы не используете switch или аналогичный для выбора между несколькими предопределенными параметрами (например, short, int, long, long long). Или, в вашем случае, Unsigned<4>, Unsigned<8>, Unsigned<256>. Размер не может быть вычислен динамически во время выполнения, любым способом.

Вы должны либо определить тип динамического размера (аналогично std::vector), где размер не параметр шаблона, так что один тип может хранить любой тип целого числа (а затем принять потерю эффективности, которая подразумевается), или принять, что размер должен быть выбран во время компиляции, и единственная опция, которую вы имеете для обработки «произвольных» целых чисел, это жестко кодировать набор предопределенных размеров и выбирать их во время выполнения.

decltype тоже не решит вашу проблему. Он довольно похож на auto, он полностью работает во время компиляции и просто возвращает тип выражения. (Тип 2+2 равен int, и компилятор знает это во время компиляции, даже если значение 4 вычисляется только во время выполнения)

1 голос
/ 11 августа 2010

Проблема, с которой вы сталкиваетесь, довольно распространена.Шаблоны разрешаются во время компиляции, в то время как вам нужно изменить свое поведение во время выполнения.Как бы вам этого не хотелось с мифическим одним дополнительным слоем косвенности , проблема не исчезнет: вы не можете выбрать тип возврата своей функции.

Так как вам нужнодля выполнения операций на основе информации времени выполнения вы должны использовать динамический полиморфизм (вместо статического полиморфизма, предоставляемого шаблонами).Это подразумевает использование динамического выделения внутри метода GetMeAnUnsigned и, возможно, возвращение указателя.

Есть несколько приемов, которые вы можете воспроизвести, например, скрытие указателя внутри класса, который предлагает открытый интерфейс и делегирует внутреннийвыделенный объект в том же стиле, что и boost::any, так что пользователь видит один тип, даже если реальный объект выбран во время выполнения.Это усложнит проектирование, я не уверен, насколько сложнее будет код, но вам нужно будет подумать о том, какой минимальный интерфейс вы должны предложить во внутренней иерархии классов, чтобы выполнить требования внешнего интерфейса.- это кажется действительно интересной проблемой ...

0 голосов
/ 11 августа 2010

Это действительно очень распространенная проблема, в прошлый раз, когда я видел, что это было с матрицами (измерения в качестве параметров шаблона и как работать со значением, предоставленным во время выполнения).

К сожалению, это неразрешимая проблема.

Проблема не является специфичной для C ++ как таковой, она специфична для строгой типизации в сочетании с проверкой во время компиляции.Например, Haskell может демонстрировать подобное поведение.

Есть 2 способа справиться с этим:

  • Вы используете switch не для создания типа, а для запуска полноговычисление, то есть main почти пусто и служит только для чтения входного значения
  • Вы используете бокс: вы помещаете фактический тип в универсальный контейнер (либо классом, созданным вручную, либо boost::any или * 1015).*), а затем, при необходимости, распакуйте значение для конкретного лечения.

Я лично предпочитаю второй подход.

Самый простой способ сделать это - использовать базовый класс (интерфейс):

struct UnsignedBase: boost::noncopyable
{
  virtual ~UnsignedBase() {}
  virtual UnsignedBase* clone() const = 0;

  virtual size_t bytes() const = 0;

  virtual void add(UnsignedBase const& rhs) = 0;
  virtual void substract(UnsignedBase const& rhs) = 0;
};

Затем вы оборачиваете этот класс в простой менеджер, чтобы упростить управление памятью для клиентов (вы скрываете тот факт, что вы полагаетесь на выделение кучи + unique_ptr):

class UnsignedBox
{
public:
  explicit UnsignedBox(std::string const& integer);

  template <size_t N>
  explicit UnsignedBox(Unsigned<N> const& integer);

  size_t bytes() const { return mData->bytes(); }

  void add(UnsignedBox const& rhs) { mData->add(rhs.mData); }
  void substract(UnsignedBox const& rhs) { mData->substract(rhs.mData); }

private:
  std::unique_ptr<UnsignedBase> mData;
};

Здесь виртуальная диспетчеризация позаботится о распаковке (несколько), вы также можете распаковать вручную, используя dynamic_cast (или static_cast, если вы знаете количество цифр):

void func(UnsignedBase* i)
{
  if (Unsigned<2>* ptr = dynamic_cast< Unsigned<2> >(i))
  {
  }
  else if (Unsigned<4>* ptr = dynamic_cast< Unsigned<4> >(i))
  {
  }
  // ...
  else
  {
    throw UnableToProceed(i);
  }
}
0 голосов
/ 11 августа 2010

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

Вам нужно иметь базовый класс Unsigned_base, из которогоUnsigned<t> пунктов производного.Затем ваш метод GetMeAnUnsigned может вернуть указатель на Unsigned_base.Затем он может быть преобразован с использованием чего-то вроде dynamic_cast<Unsigned<8> >().

. Возможно, вам лучше иметь функцию, возвращающую объединение возможных типов unsigned<n>, но это сработает, только если ваш тип соответствует требованиямбудучи членом профсоюза.

РЕДАКТИРОВАТЬ: Вот пример:

struct UnsignedBase
{
    virtual ~UnsignedBase() {}
};

template<std::size_t c>
class Unsigned : public UnsignedBase
{
    //Implementation goes here.
};

std::auto_ptr<UnsignedBase> GiveMeAnUnsigned(std::size_t i)
{
    std::auto_ptr<UnsignedBase> result;
    switch(i)
    {
    case 42:
        result.reset(new Unsigned<23>());
    default:
        result.reset(new Unsigned<2>());
    };
    return result;
}
...