C++: Виртуальный шаблон типа Erasure Обходной путь

Я использую шаблон проектирования стирания типа, чтобы предоставить независимый от шаблона интерфейс для одного из моих классов шаблонов. Однако я столкнулся с проблемой, что один из методов, которые я хочу раскрыть, который объединяет два экземпляра шаблона в третий экземпляр с другим аргументом параметра константы шаблона, по-видимому, требует виртуальных методов шаблона, которые являются незаконными.

Это мой код:

#include <stdlib.h>

template<size_t N>
class bar
{
    template<size_t M>
    bar<M+N> fuse(const bar<M>& rhs) { return bar<M+N>(); }
};

class bar_any_N 
{
private:
    class abstract_base
    {
        virtual bar_any_N fuse(const abstract_base* rhs) = 0;

        template<size_t M>
        virtual bar_any_N fuse_accept(const bar<M>& lhs) = 0;
    };

    template<size_t N>
    class wrapper : public abstract_base
    {
    private:
        bar<N> m_bar;
    public:
        wrapper(const bar<N>& the_bar) : m_bar(the_bar) { }

        bar_any_N fuse(const abstract_base* rhs) { return rhs->fuse_accept(*this); }

        template<size_t M>
        bar_any_N fuse_accept(const bar<M>& lhs) { return lhs.m_bar.fuse(this->m_bar) }
    };

    abstract_base* m_ptr;
public:
    template<size_t N> 
    bar_any_N(const bar<N>& the_bar) { m_ptr = new wrapper<N>(the_bar); }

};

int main()
{
    bar<1> b1;
    bar<2> b2;
    bar_any_N b1_erased(b1);
    bar_any_N b2_erased(b2);

    bar_any_N b3 = b1_erased.fuse(b2_erased);

    return 0;
}

Есть ли у кого-нибудь другой способ реализовать это, не требующий члена виртуального шаблона?

РЕДАКТИРОВАТЬ: Целью этого «независимого от шаблона интерфейса» является передача векторов экземпляров панели с различными параметрами шаблона в функции:

std::vector< bar_any_N > vec; 
vec.push_back(bar<2>()); 
vec.push_back(bar<5>()); 
foo_func(vec); 

РЕДАКТИРОВАТЬ:

Вот более простой рабочий пример с методом печати вместо описанного выше метода предохранителя, который показывает, как я ХОЧУ, чтобы это работало:

http://codepad.org/8UbJguCR


person Sam Manzer    schedule 04.11.2013    source источник
comment
fuse_accept проблема? Если да, то почему бы вам не реализовать это не виртуально в abstract_base?   -  person Jonas Bötel    schedule 04.11.2013
comment
abstract_base не имеет (и не может, поскольку это заставило бы его быть шаблонным) члена m_bar, поэтому метод должен быть виртуальным, чтобы иметь доступ к этому члену.   -  person Sam Manzer    schedule 04.11.2013
comment
что именно вы подразумеваете под «независимым от шаблона интерфейсом»   -  person Johan Lundberg    schedule 04.11.2013
comment
@JohanLundberg, пожалуйста, смотрите мою правку выше   -  person Sam Manzer    schedule 04.11.2013
comment
Существует ли верхний предел для M? Если нет, проблема. Во время стирания типа должны быть реализованы все методы, которые могут быть созданы. Без верхнего предела M это невозможно (ну, максимум size_t — это верхний предел, так что просто непрактично). Почему N должен быть параметром времени компиляции?   -  person Yakk - Adam Nevraumont    schedule 04.11.2013
comment
Верхнего предела на M нет, поэтому, возможно, это невозможно :( N — параметр времени компиляции для удобства отладки; в реальной жизни bar‹N› — это тензор порядка N, и его методы берут списки блоков строго длины Н.   -  person Sam Manzer    schedule 04.11.2013
comment
@SamManzer Подожди, что? N является константой времени компиляции только для удобства отладки?   -  person Yakk - Adam Nevraumont    schedule 04.11.2013
comment
@Yakk в основном да; это также делает реализацию класса несколько чище (все массивы-члены имеют размер N и т. д.), но основное преимущество заключается в том, что компилятор улавливает его, если я запрашиваю смещение блока в трехмерном тензоре (требуется 3 индекса) и передать только 2 индекса. Если бы это не было константой, нужно было бы использовать std::vector или что-то в этом роде и не иметь такой безопасности.   -  person Sam Manzer    schedule 04.11.2013
comment
Я думаю, что понимаю, что вы хотите сделать, но я думаю, что вопрос был бы более ясным, если бы вы действительно представили код, который правильно скомпилирован, но не делает то, что вы хотите. У вас есть код, который не компилируется, и вы просите нас понять, чего вы на самом деле хотите.   -  person jxh    schedule 04.11.2013
comment
@jxh Я добавил ссылку внизу сообщения, которая показывает, как этот код стирания типа работает для простого метода печати. Я сталкиваюсь с этой предполагаемой потребностью в виртуальных шаблонах, когда хочу использовать метод с двумя шаблонными аргументами, такими как метод fuse выше.   -  person Sam Manzer    schedule 04.11.2013


Ответы (1)


Сделайте стирание наизнанку.

struct bar_raw
{
  std::size_t N;
  explicit bar_raw( std::size_t n ):N(n) {}
  bar_raw fuse( const bar_raw& rhs ) { return bar_raw(N+rhs.N); }
};
template<size_t N>
struct bar: bar_raw
{
  bar():bar_raw(N) {}
  template<size_t M>
  bar<M+N> fuse(const bar<M>& rhs) { return bar<M+N>(); }
};

Сохранить все состояние в bar_raw. Иметь красивый интерфейс, который помогает писать код в bar<N>, например n-арные индексы.

При передаче bar<N> для ввода стирания вы игнорируете компонент bar<N> и вводите стирание bar_raw, что довольно тривиально.

Вы можете назвать bar_raw своим bar_any, если хотите, или вы можете оставить bar_raw в качестве детали реализации и обернуть его в bar_any, и это будет красиво.

person Yakk - Adam Nevraumont    schedule 04.11.2013
comment
Просто добавлю свои два цента: единственная «проблема» заключается в том, что система типов для всей ситуации становится немного запутанной и странной; ожидайте выполнения большого количества преобразований типов или свертывания собственного RTTI, если вы хотите последовательно получать шаблонный подкласс. Это то, что вы получаете за попытку подорвать ограничение отсутствия виртуальных шаблонов. Или за попытку превратить бесконечность в конечное число. Это в основном одна и та же проблема для компилятора, сформулированная в любом случае. :-П - person user; 04.11.2013
comment
@Atash, поэтому я думаю, что bar_raw должно быть личной информацией. Случай со стертым типом не может раскрыть шаблонный подкласс, так как он стертый тип. При работе с bar<N> его симпатичный интерфейс проделывает кучу магии типов для создания результирующего типа и принимает в качестве аргументов только типы bar<M>, поэтому он может выполнять математические операции с этими типами без каких-либо хакерских действий. Внутренний bar_raw в конечном итоге должен дублировать большую часть этой работы, но он управляет состоянием времени выполнения (которое вы доказываете и утверждаете, что оно совпадает с состоянием времени компиляции). Объект стирания заботится только о состоянии выполнения. - person Yakk - Adam Nevraumont; 05.11.2013
comment
Я думаю, что я, вероятно, сделаю что-то подобное; однако кто-нибудь знает, есть ли альтернатива использованию RTTI/dynamic_cast для приведения указателей abstract_base к соответствующему типу оболочки N? - person Sam Manzer; 05.11.2013
comment
@SamManzer Если вы хотите поддерживать бит RTTI, вам нужно добавить некоторую информацию о времени выполнения. Это означает наличие дополнительного поля в вашем классе bar, удерживаемого каждым объектом (как указано выше), или синтаксического анализа результата typeid() (blech) и т. д. Лучшее, что вы можете сделать (на самом деле) для кастинга, — это оператор jump-table/switch для всех полезных значений N для bar<N> для каждого бита кода, который вы хотите иметь, используя соответствующий тип wrapper<N>, а не какой-то базовый класс. Вы можете трамплинить это, используя некоторое метапрограммирование шаблонов, но это будет больно. Возможно, было бы разумнее просто использовать прямой полиморфизм. - person user; 05.11.2013