Здравствуйте, watchmaker, Вы писали:
W>Например, возможность не делать вручную квадратичное число определений, или возможность задать действие в случае невозможности совместить типы, а не просто выдавать ошибку undefined reference.
В том варианте, что я предлагаю присутствует возможность не делать квадратичное число определений вручную. Но если кто-то хочет сделать определения вручную, то он может этим заняться для уменьшения времени компиляции.
BFE>>- указанный параметр шаблонной функции должен соблюдать требования накладываемые шаблонной функцией на тип параметра. BFE>>Так что если проект не собирается, то кто-то сделал ошибку. W>Так кто из двух программистов из примера выше сделал ошибку? Первый, написавший logger->write<MyNewType> её не сделал, так как MyNewType удовлетворяет всем ограничениям базового класса. Второй вообще код не менял. Код сам сломался. Или, быть может, их ошибкой была сама идея использовать виртуальные шаблонные функции!? Я уже почти уверен, что именно это
Почему тот, кто написал logger->write<MyNewType> не может добавить в свой файл строчку #include, которая решить его проблему с undefined reference? Очевидно, что написавший logger->write<MyNewType> забыл добавить определение функции, которое лежит в одном из хедеров. Всё равно, что написать:
Что удивительного в том, что линкер не соберёт такой проект?
BFE>>А почему, собственно, SFINAE не будет работать? Что этому помешает? W>Тут, как мне кажется, сломана заключительная часть — что делать если ни одна подстановка не сработала. Если у меня есть шаблонная функция Foo<>, а я вызываю Foo<Bar> с несовместимым типом Bar, то я могу поймать этот момент через ellipsis, например, и задать поведение в этом случае. W>В вышеприведённой же реализации linker сразу смотрит на наличие единственного символа и всё заканчивается на обнаружении его отсутствия.
Если есть декларация функции с ellipsis, но нет определения, то linker не сможет слинковать проект.
W>Нет простой возможности задать поведение сразу для группы плохих комбинаций. В тех же мультиметодах такая возможность есть — если не определена частная реализация, то откатываемся к более общей. А linker'у это будет намного сложнее объяснить.
Ну причём тут мультиметоды? Кто-то предлагает реализацию шаблонных мультиметодов?
W>Короче говоря, не хватает аналога записи void B::virt_fun<int>(int) = default;, но которая будет задавать действие когда default подстановка не удалась. Возможно какой-то синтаксис вроде non-override, позволяющий взять в таком случае реализацию из базового класса (хотя тут можно что-то случайно сломать и не сразу заметить; да и ругаться в runtime может скорее понадобится, а не наследовать), или ввести opaque наследование для классов, чтобы наличие метода Foo<Bar> в одной части программы не вызывало необходимости наличия такой же специализации у наследника в другой части программы (если программист честно пообещает, что это части никак не связаны).
Да зачем же такое нужно? Если чего-то не удалось, то это ошибка в программе. А то, что вы предлагаете — прямой путь к нарушению ODR: может сложится ситуация, когда одинаковый код вызывает разные функции в зависимости от того, какие хедеры подключены.
Не забывайте, что речь идёт о шаблонных функциях, а шаблонные функции требуют включения в модуль всех определений шаблонной функции. Что же удивительного в том, что при отсутствии части определений проект не соберётся?
Здравствуйте, B0FEE664, Вы писали:
BFE>Сейчас и того хуже, потому что все эти функции придется писать руками с определением тела и т.д. и т.п.
Сейчас не хуже, сейчас нет лишних усложнений системы ради сомнительного функционала. Если уже вводить кросс-модульную кодогенерацию, то подходить к этому нужно основательно, спрятать под это внешние определения шаблонов и много других потенциальных ништяков. Сами по себе виртуальные шаблоны этого не стоят, я считаю.
BFE>Ничто не мешает определить несколько специализаций и для шаблонной виртуальной функции.
Разница в том, что здесь придется определять специализации, которые будут заранее не нужны, для функций, которые никогда не будут вызваны. Короче, это обсуждалось достаточно.
W>>Увы, это нонсенс для С++. В чистом виде это "статический полиморфизм времени исполнения", то есть то, что поставит весь С++ с ног на голову. BFE>Это совмещение статического полиморфизма с динамическим.
Что есть нонсенс для современного С++. А чтобы он нонсенсом быть перестал, нужно перестраивать всю идеологию кодогенерации.
BFE>Это другая тема для других задач.
Разве? А мне показалось, что эта тема вполне разумно решила именно нашу задачу.
Здравствуйте, Went, Вы писали: BFE>>Сейчас и того хуже, потому что все эти функции придется писать руками с определением тела и т.д. и т.п. W>Сейчас не хуже, сейчас нет лишних усложнений системы ради сомнительного функционала.
Про виртуальные функции так же говорили. W>Если уже вводить кросс-модульную кодогенерацию, то подходить к этому нужно основательно, спрятать под это внешние определения шаблонов и много других потенциальных ништяков.
Я как раз предлагаю не делать кросс-модульную кодогенерацию, так как эта слишком сложно и не все с эти справятся. W>Сами по себе виртуальные шаблоны этого не стоят, я считаю.
Мне очень нужны виртуальные шаблонные методы. Уже который год. BFE>>Ничто не мешает определить несколько специализаций и для шаблонной виртуальной функции. W>Разница в том, что здесь придется определять специализации, которые будут заранее не нужны, для функций, которые никогда не будут вызваны. Короче, это обсуждалось достаточно.
Можно посмотреть, где это обсуждалось? W>>>Увы, это нонсенс для С++. В чистом виде это "статический полиморфизм времени исполнения", то есть то, что поставит весь С++ с ног на голову. BFE>>Это совмещение статического полиморфизма с динамическим. W>Что есть нонсенс для современного С++. А чтобы он нонсенсом быть перестал, нужно перестраивать всю идеологию кодогенерации.
Зачем? Почему нельзя сделать как я предлагаю? BFE>>Это другая тема для других задач. W>Разве? А мне показалось, что эта тема вполне разумно решила именно нашу задачу.
А как вы видите себе использование шаблонных виртуальных методов?
Я, например, вижу применение позволяющие писать хранение разнотипных объектов в одном контейнере и их обработку с помощью единого метода.
Конкретно это может выглядеть так:
— создаём объект очередь;
— добавляем в очередь данные произвольного типа;
— создаём диспетчер сообщений, содержащий обработчики для каждого из типов;
— вызываем у очереди функцию pop передав ей в качестве параметра диспетчер. Эта функция вызывает обработчик находя соответствие между типом данных и обработчиком.
Насколько я знаю, сейчас нет нормальных способов написать такую очередь для произвольного набора типов. Сейчас это делают либо с помощью RTTI, либо с помощью стирателя типа, либо с помощью жутких приведений типов, либо для ограниченного набора типов. Если знаете как написать такую очередь красиво, скажите мне, пожалуйста.
А вот если бы у нас были виртуальные шаблонные методы это выглядело бы довольно красиво:
class HolderBase
{
public:
template <class Fn>
virtual void Dispatch(Fn f)
{
// it should call Holder<T>::Dispatch(f);
}
};
template <class T>
class Holder : public HolderBase
{
public:
Holder(const T& t)
: m_t(t)
{
}
template <class Fn>
virtual void Dispatch(Fn f)
{
f(t);
}
private:
T m_t;
};
class Queue
{
public:
template <class T>
void push(T t)
{
m_queue.push(new Holder<T>(t));// TODO smart ptr?
};
template <class TDispatcher>
void pop(TDispatcher oDispatcher)
{
assert(!m_queue.empty());
HolderBase* p = m_queue.front(); // TODO RAII
m_queue.pop();
p->Dispatch(oDispatcher);
delete p;
};
bool empty() const
{
return m_queue.empty();
}
private:
std::queue<HolderBase*> m_queue;
};
Как тут помогут авто-функции я не вижу.
весь код
#include <assert.h>
#include <iostream>
#include <queue>
#include <string>
class HolderBase
{
public:
template <class Fn>
void Dispatch(Fn f)
{
// it should call Holder<T>::Dispatch(f);
}
};
template <class T>
class Holder : public HolderBase
{
public:
Holder(const T& t)
: m_t(t)
{
}
template <class Fn>
void Dispatch(Fn f)
{
f(t);
}
private:
T m_t;
};
class Queue
{
public:
template <class T>
void push(T t)
{
m_queue.push(new Holder<T>(t));// TODO smart ptr?
};
template <class TDispatcher>
void pop(TDispatcher oDispatcher)
{
assert(!m_queue.empty());
HolderBase* p = m_queue.front(); // TODO RAII
m_queue.pop();
p->Dispatch(oDispatcher);
delete p;
};
bool empty() const
{
return m_queue.empty();
}
private:
std::queue<HolderBase*> m_queue;
};
//-------------------------------------------------------------------------------------------DispatcherSettemplate<class ...TDispatchers>
class DispatcherSet;
template<class TDispatcher, class ...TDispatchers>
class DispatcherSet<TDispatcher, TDispatchers...> : public TDispatcher, public DispatcherSet<TDispatchers...>
{
public:
DispatcherSet(TDispatcher oDispatcher, TDispatchers... oDispatchers)
: TDispatcher (oDispatcher),
DispatcherSet<TDispatchers...>(oDispatchers...)
{
}
using TDispatcher::operator();
using DispatcherSet<TDispatchers...>::operator();
};
template<class TDispatcher>
class DispatcherSet<TDispatcher> : public TDispatcher
{
public:
DispatcherSet(TDispatcher oDispatcher)
: TDispatcher(oDispatcher)
{
}
using TDispatcher::operator();
};
template<class ...TDispatchers>
DispatcherSet<TDispatchers...> CreateDispatchers(TDispatchers... oDispatchers)
{
return DispatcherSet<TDispatchers...>(oDispatchers...);
}
//-------------------------------------------------------------------------------------------void IntLog(int n)
{
std::cout << "IntLog: n = " << n << std::endl;
}
void StrLog(const std::string& str)
{
std::cout << "StrLog: str = " << str << std::endl;
}
//-------------------------------------------------------------------------------------------int main(int argc, char* argv[])
{
Queue q;
int n = 0;
std::string str("asdf");
q.push(n);
q.push(str);
auto oDispatcher = CreateDispatchers(
[](int n ){ IntLog(n); },
[](const std::string& str){ StrLog(str); },
[](float f ){ std::cout << "float = " << f << std::endl; }
);
// test
//oDispatcher(n);
//oDispatcher(n);
//oDispatcher(str);while(!q.empty())
q.pop(oDispatcher);
return 0;
}
Здравствуйте, B0FEE664, Вы писали: BFE>Насколько я знаю, сейчас нет нормальных способов написать такую очередь для произвольного набора типов. Сейчас это делают либо с помощью RTTI, либо с помощью стирателя типа, либо с помощью жутких приведений типов, либо для ограниченного набора типов. Если знаете как написать такую очередь красиво, скажите мне, пожалуйста.
Для такого общего случая автофункции не помогут. Поэтому я и писал про половину Но проблема в том, что и ваше решение содержит слишком много костылей. Поэтому я и говорю, что еще можно поспорить — какое решение лучше: RTTI, стиратели, жуткие приведения или "виртуальные шаблоны". Ведь недостаточно будет "докомпилировать все недостающие виртуальные перекрытия" для всех встреченных (и не встреченных, но подсказанных программистом) вариантов. Придется отложить и компиляцию всех функций, которые вызывают эту виртуальную функцию (нужно же будет знать, из какого многообразия перекрытий мы выбираем), и даже тех функций, которые вызывают любые другие виртуальные функции этого класса (ведь размер виртуальной таблицы будет не определен до момента полной компиляции, а значит смещения в этой таблице будут скакать). Мало того, так как любой вызов виртуальной функции будет по факту являться поводом для инстанции нового экземпляра, мы легко получим случаи, когда вместо определения диспетчера для int нам потребуется определение оного для const int, для unsigned int, сonst unsigned int, const int&, short, char, long и прочего зоопарка. Ведь как компилятор догадается, что нужно использовать готовую реализацию, а не затребовать новую? Вы предлагаете делать связки определений, которые потом "включать все вместе в нужных местах". Но это противоречит идее произвольного набора типов. Набор уже не произволен, а ограничен тем, что включено. А если он ограничен, то можно использовать обычного Visitor-а и не ломать компиляторы
Здравствуйте, Went, Вы писали:
W> Но проблема в том, что и ваше решение содержит слишком много костылей. Поэтому я и говорю, что еще можно поспорить — какое решение лучше: RTTI, стиратели, жуткие приведения или "виртуальные шаблоны". Ведь недостаточно будет "докомпилировать все недостающие виртуальные перекрытия" для всех встреченных (и не встреченных, но подсказанных программистом) вариантов.
Объясните, почему недостаточно? В каком случае недостаточно?
W> Придется отложить и компиляцию всех функций, которые вызывают эту виртуальную функцию (нужно же будет знать, из какого многообразия перекрытий мы выбираем),
Вообще-то все разнообразие задано при декларации класса и многообразие перекрытий его поменять не может.
W>и даже тех функций, которые вызывают любые другие виртуальные функции этого класса (ведь размер виртуальной таблицы будет не определен до момента полной компиляции, а значит смещения в этой таблице будут скакать).
Это не аргумент. Это отговорка.
W>Мало того, так как любой вызов виртуальной функции будет по факту являться поводом для инстанции нового экземпляра, мы легко получим случаи, когда вместо определения диспетчера для int нам потребуется определение оного для const int, для unsigned int, сonst unsigned int, const int&, short, char, long и прочего зоопарка. Ведь как компилятор догадается, что нужно использовать готовую реализацию, а не затребовать новую?
Насколько я понимаю, компиляторы уже сейчас с этим справляется объединяя код для void TFun<int>(int) и void TFun<const int>(const int) функции template <class T> void TFun(T t), так что я не вижу здесь проблемы.
Даже если и потребуется однострочное определения для каждого типа, то это всё легче, чем писать полные определения для каждого из типов, как это делается сейчас.
W>Вы предлагаете делать связки определений, которые потом "включать все вместе в нужных местах". Но это противоречит идее произвольного набора типов. Набор уже не произволен, а ограничен тем, что включено. А если он ограничен, то можно использовать обычного Visitor-а и не ломать компиляторы
Если вы используете обычный Visitor, то вам приходится руками писать весь тот "зоопарк" функций в каждой отдельной реализации этого паттерна. В каждой реализации, Went! Для каждого отдельного набора типов! Полноценную реализацию каждой функции для каждого отдельного типа при каждом использовании паттерна в каждом отдельном проекте! А с виртуальными шаблонными весь паттерн укладывается в один-два экрана кода без потери общности и скорости вызовов. Но всех смущают однострочные определения под конкретные типы. Пойду выпью воды...
Здравствуйте, B0FEE664, Вы писали:
BFE>Объясните, почему недостаточно? В каком случае недостаточно?
Объясняю как раз ниже:
BFE>Вообще-то все разнообразие задано при декларации класса и многообразие перекрытий его поменять не может.
Ну смотрите. У нас есть класс:
Какое смещение должен взять компилятор для определения адреса X::omg? Пока мы не узнаем все многообразие вызовов foo и bar, встречающееся в программе, мы этого не узнаем. А к чему это приведет? К тому, что любой код, использующий данный вызов будет вынужден перекомпилироваться при каждой перелинковке программы.
BFE>Если вы используете обычный Visitor, то вам приходится руками писать весь тот "зоопарк" функций в каждой отдельной реализации этого паттерна. В каждой реализации, Went! Для каждого отдельного набора типов! Полноценную реализацию каждой функции для каждого отдельного типа при каждом использовании паттерна в каждом отдельном проекте! А с виртуальными шаблонными весь паттерн укладывается в один-два экрана кода без потери общности и скорости вызовов. Но всех смущают однострочные определения под конкретные типы. Пойду выпью воды...
На вариадиках можно замутить довольно элегантно.
Здравствуйте, Went, Вы писали:
W>Здравствуйте, B0FEE664, Вы писали:
BFE>>Объясните, почему недостаточно? В каком случае недостаточно? W>Объясняю как раз ниже:
W>
W>void f(X* px)
W>{
x->omg();
W>}
W>
W>Какое смещение должен взять компилятор для определения адреса X::omg? Пока мы не узнаем все многообразие вызовов foo и bar, встречающееся в программе, мы этого не узнаем.
Вообще говоря, нет такой уж необходимости решать этот вопрос на этапе компиляции. У тебя задан вопрос «какое брать смещение?», а я могу задать другой вопрос — «смещение относительно чего?».
Если рассмотреть самую популярную реализация обычных виртуальных функций через vtable, то там смещение берётся от начала соответствующей таблицы. И хотя список записей в этой таблице и известен на этапе компиляции, но её адрес — нет.
Да, этот адрес записан в экземпляре объекта, но это уже runtime, а сам он вычисляется лишь на этапе линковки. Но это совершенно не мешает компилятору создавать код.
Просто компилятор может генерировать инструкции, которые загружают константы, которые вычисляются лишь линкером. Что он обычно и делает: в коде конструктора оставляются несколько инструкций, которые записывают в vptr экземпляра нужный указатель, но при этом после компиляции вместо этого указателя там содержится заглушка, а линкеру оставляются инструкции подставить на это место актуальное значение когда-то потом.
Так что возвращаясь к вопросу о смещении X::omg — оно может быть вычислено линкером. А компилятор сможет загрузить это смещение, например, из константы в памяти, куда значение положит линкер (либо линкер может даже напрямую внутрь самой инструкции нужное смещение записать).
Да, такая реализация (с неизвестной структурой таблиц на этапе компиляции) обладает многими недостатками, но нет принципиальной ограничений, из-за которых было бы нельзя усложнить линкер и переложить на него задачу сборки таблиц.
Здравствуйте, watchmaker, Вы писали:
W>Экспорт шаблонов — это отличная база для реализации шаблонных виртуальных функций. А если у тебя есть шаблонные виртуальные методы, то ты сразу получаешь экспорт шаблонов. По сути в реализации вся сложность именно в экспорте, а виртуальность поверх него уже относительно просто добавить. Но экспорт не реализуется за приемлемую стоимость.
Даже есть бы был экспорт, всё равно vtable пришлось бы как-то динамически хачить...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
К>Вообще, если бы дженерики появились в неуправляемом С++, это было бы очень здорово.
А что мешает?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
К>>Вообще, если бы дженерики появились в неуправляемом С++, это было бы очень здорово. E>А что мешает?
1) продумать неинтрузивные словари функций (в дополнение к интрузивным — таблицам виртуальных методов) и способы их передачи.
2) придумать удобный боксинг данных для унификации лэяута (не ограничиваться одним лишь shared_ptr и всякими ptr_контейнерами); удобный — в идеале, это неявный.
3) взять спецификацию C++CLI и перетащить синтаксис в стандарт, а реализацию парсера и тайпчекера в компиляторы.
Это всё — время, деньги, люди...
Микрософту это, вроде, не особо нужно, C++CLI бедный родственник. А остальные будут делать с нуля.