|
|
  |
Система, управляемая событиями и SST(super-simple tasker), Выделено из "ООП. Классы и динамические объекты" |
|
|
|
Sep 8 2016, 21:10
|
Профессионал
    
Группа: Свой
Сообщений: 1 047
Регистрация: 2-12-06
Из: Kyiv, Ukraine
Пользователь №: 23 046

|
NodeJS свободно может работать без ОС и потоков вообще (и работает - есть даже реализации под Embedded ссылки я приводил), в то время, как классический многопоточный код на C работать без ОС не сможет - ему нужны треды, мютексы и семафоры. Точно так же и SST - может работать как поверх любой ОС, так и сам на голом МК, хоть на AVR. Он не привязан к железу и свободно реализовывается в 200-300 строк на С++. Да, у меня часть написана на ассемблере(строчек на 20) но это было сделано только ради небольшого увеличения производительности на конкретном камне(Кортекс). Ессно в конкретном тесте Node - это процесс по верх ОС, но это вынужденная мера, тк весь мир работает на линуксах и виндах, а не на голом железе. И та же идея Node запросто работает на голом МК вообще без единой библиотеки. И может обрабатывать гораздо больше событий(не обязательно однотипных - любых, работа с периферией, внешними устройствами, итд) на том же МК, чем классическая FreeRTOS или любая другая. При чем код получается даже проще, а если начать мыслить в асинхронном стиле, то и подавно. Мне, например было довольно сложно переключится с асинхронной модели мышления к синхронной, когда я делал сервак и клиент на С, много времени заняло по сравнению с Node. 99% задач, которые делают на FreeRTOS и подобных - решаются на SST более простым, натуральным способом и требуют в разы меньше памяти. Цитата Надеялся увидеть как вы с SST будете изобретать велосипед и отдавать клиентам данные порциями, чтобы хоть как-то обслуживать их всех параллельно В этом нет необходимости. Наоборот, такая необходимость есть в многопоточной модели(посмотрите мой сишный код выше), тк операция чтения блокирующая, и нужно читать файл порциями, тк попросту я не могу загнать весь файл в память иначе даже при 20 потоках она кончится(или включится подкачка). В асинхронном стиле этого делать не нужно - данные идут через высокоуровневые структуры и какими там порциями передаются я знать не знаю. Скорее всего такими же, какими отдает жесткий диск, вернее его кеш. У меня в моем SST данные идут порциями, натуральными для конкретного устройства. Например, если это флешка - то страницами. При чем это все происходит через высокоуровневые структуры и пользователь не видит что там физически происходит. Вот пример из моего же кода. Открытие, чтение файла и передача его в сокет в однопоточном асинхронном Node Код var readable = fs.createReadStream('file.txt'); readable.pipe(socket); При чем если написать принт после этих строчек, то принт сработает до окончания передачи файла, а не после (если файл достаточно большого размера или канал медленный. А вот в многопоточном синхронном С: Код FILE* fd = fopen("file.txt","r"); if(fd==0){ perror("fopen"); goto exit1; }
// read and send file - blocking! char buffer[4096]; while(1){ // read chunk size_t r = fread(buffer, 1, sizeof(buffer), fd); if(r<=0)break; // send r = send(sock, buffer, r, 0); if(r<=0){ perror("Thread_connection: socket error"); break; } }; fclose(fd); Где проще? В моем SST все примерно так и выглядит - передача через высокоуровневые обьекты(очереди/пайпы называйте как хотите) и все прозрачно для пользователя, и очень быстро - оверхеда минимум, меньше уже практически невозможно. Цитата Уже не один человек высказал мнение, что SST это не так круто как вам кажется и имеет кучу ограничений и проблем. Однако вы по прежнему упрямствуете и, как вы сами выразились, вдуваете себе в голову On wink.gif Покажите чем реально SST хуже потоков? Какую задачу на SST реализовать сложнее, чем на потоках? Давайте мне любой многопоточный код и я его переведу в более красивый и быстрый асинхронный SST-шный  Мы говорим о реальных практических задачах, а не где то там что-то там требует тайм-кванты. Давайте реальную задачу с тайм-квантами и я покажу как от них избавится  Вообще я программирую немного иначе - я не добавляю строчки кода, а удаляю. Чем больше я удаляю строк кода из своих программ, тем я считаю их более совершенными. Самый совершенный код это код на 0 строк, конечно такое на практике не достижимо. Вот пример аналогичного кода(чтение из терминала через Uart и передача другому чипу) в моем SST: Код void Term::UartReader::operator()( T_RxFifo_Base* fifo, const uint8_t* ptr, int len) { term->telit->cmux.send(TelitMux_Gsm, ptr, len, [fifo](){ fifo->signal_dataAccepted(); }); } По-больше кода чем у Node 3 строчки против одной, но это C++, такой он есть verbose язык.. Хотя могу и однй сделать, запросто, если будет такая необходимость.
|
|
|
|
|
Sep 9 2016, 02:16
|

Местный
  
Группа: Свой
Сообщений: 327
Регистрация: 24-06-06
Из: Томск
Пользователь №: 18 328

|
2brag В приведенном вами коде проскакивает Код delegate<void()> Может быть выложите реализацию делегатов? Сам использую свой велосипед и собираю велосипеды других с целью повышения образования.
|
|
|
|
|
Sep 9 2016, 04:53
|
Знающий
   
Группа: Свой
Сообщений: 721
Регистрация: 23-10-08
Из: next to Odessa
Пользователь №: 41 112

|
Цитата(Herz @ Sep 8 2016, 22:38)  Поступило предложение тему разделить и основное обсуждение озаглавить "Система, управляемая событиями и SST(super-simple tasker)". Будут возражения? Или переименуем всю тему? Автор, что думаете? Конечно надо бы разделить тему на усмотрение модератора. Мне, к примеру, обсуждение уже совсем не понятным становится. Мой вопрос скорее относился к вводным понятиям о кучах, да и тема мною сформулирована вроде бы четко. Хотя интересно было узнать то, что помимо стандартных new/delete, существуют и свои средства (стратегии) для управления кучами. В институте был курс "Организация вычислительных процессов в ЭВМ", в частности с понятиями об операционных системах и их работе с очередями пользовательских программ, были и книги по этой теме. А сейчас какие есть книги (ru/eng) на эту же тематику, но c учетом новых подходов?
|
|
|
|
|
Sep 9 2016, 06:12
|
Профессионал
    
Группа: Свой
Сообщений: 1 047
Регистрация: 2-12-06
Из: Kyiv, Ukraine
Пользователь №: 23 046

|
Цитата Хотя интересно было узнать то, что помимо стандартных new/delete, существуют и свои средства (стратегии) для управления кучами. dew/delete никуда не деваются, но мы их можем перегружать и иметь свою реализацию. Потом есть всякие smart-pointerы и reference count-ы - это для того, чтобы голова за delete не болела. Но их надо использовать с осторожностью(равно как и delete) Динамическая память напрямую связана с той средой, в которой работаете. В многопоточной среде динамическая память приводит к большим тормозам и проблемам - поскольку на время работы аллокатора надо блочить все потоки и планировщик, а возможно и часть прерываний. В то время как в SST-модели в этом нет необходимости, блочится только часть системы, а не практически вся. Цитата Может быть выложите реализацию делегатов? Сам использую свой велосипед и собираю велосипеды других с целью повышения образования. Выложу ниже. Велосипеды/не велосипеды, но эта реализация отлично работает многие годы. А сделал я ее потому что когда я попытался применить нормальный std::function на Atmega8, то это сразу сожрало всю оператирку(и все рано ее не хватило), потянуло за собой кучу ненужного мусора. В итоге сделал этот велосипед na 80 строк(вместо нескольких тысяч в std) за час. Держите статическую версию, в большинстве случаев ее достаточно. А на слабых МК (до 8-16кб оперативки) только она и уместна CODE template<class T, int d_size=1> class delegate;
template<class TRet, class... TArgs, int d_size> class delegate<TRet(TArgs...),d_size> { public: delegate(){}
// copy constructor and copy operator= template<int size_another> delegate(const delegate<TRet(TArgs...),size_another>& another){ operator=(another); }
template<int size_another> delegate& operator=(const delegate<TRet(TArgs...),size_another>& another){ static_assert(sizeof(another) <= sizeof(*this), "RHS too large"); wrapper_ = another._private_get_wrapper(); for(unsigned i=0; i<sizeof(another._private_get_data()) / sizeof(data_.vp[0]); i++){ data_.vp[i] = another._private_get_data().vp[i]; } return *this; }
// lambda, static function and function object template<class TLambda> delegate(const TLambda& lambda){ setFunction(lambda); }
template<class TLambda> delegate& operator=(const TLambda& lambda){ return setFunction(lambda); }
// For lambda, static function and function object template<class TLambda> delegate& setFunction(const TLambda& lambda){ struct Wrapper{ Wrapper(const TLambda& lambda) : la(lambda) {}
static TRet wrapper(void* data, TArgs ... args){ Wrapper *w = static_cast<Wrapper*>(data); return w->la(args...); }
void* operator new(size_t, void *place){ return place; } TLambda la; }; static_assert(sizeof(Wrapper) <= sizeof(data_), "Wrapper too large");
new(&data_) Wrapper(lambda); wrapper_ = &Wrapper::wrapper; return *this; }
// invoke wrapper TRet operator()(TArgs ... args){ return wrapper_(&data_, args...); }
TRet operator()(TArgs ... args)const{ return wrapper_(const_cast<t_data*>(&data_), args...); }
private: typedef TRet (*t_wrapper)(void*, TArgs...);
t_wrapper wrapper_; union t_data{ void *vp[d_size]; } data_;
// really private public: const t_wrapper& _private_get_wrapper()const{ return wrapper_; } const t_data& _private_get_data()const{ return data_; } }; Несмотря на такое изобилие кода(который делается 1 раз) на самом деле для выполнения делегата компилятор генерирует 2 инструкции(на кортексе) - чтение указателя wrapper и прыжок на него  А тело самой функции-делегата встраивает в wrapper, фактического вызова функции внутри wrappera нет. Создание делегата тоже очень простая операция - запись указателя(одна инструкция) и запись данных(обычно тоже одна инструкция, в зависимости от обьема данных - сколько слов, столько и инструкций или одна STM для всех)
|
|
|
|
|
Sep 9 2016, 07:36
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Очень бы хотелось увидеть как бы вы обслуживали много клиентов, рисовали GUI и делали еще то что нужно и именно на голой системе и именно со своим SST и чтоб в масштабах ВСЕЙ системы был бы один стек и один поток. Собственно это то, с чего мы начинали нашу дискуссию. Цитата NodeJS свободно мо жет работать без ОС и потоков вообще (и работает - есть даже реализации под Embedded ссылки я приводил), Кто асинхронный ввод/вывод то будет обслуживать? Всё это твёрдо стоит на крепких ногах операционной системы http://man7.org/linux/man-pages/man7/aio.7.htmlhttps://msdn.microsoft.com/ru-ru/library/wi...3(v=vs.85).aspxИ для того чтобы организовать то-же самое вам придется реализовать примерно такие-же механизмы в своей системе. И вот тут уже будет существенная разница между SST + один стек на всё провсё и нормальным подходом! Хватит уже нам тут рекламировать как хорошо и естественно всё проходит без блокировок, проснитесь и откройте глаза ) Цитата Вот пример из моего же кода. Открытие, чтение файла и передача его в сокет в однопоточном асинхронном Node Показать как мало кода нужно на верхнем уровне, когда всё завёрнуто красиво мы все можем. Это уже больше похоже на какое-то поклонение высокоуровневому коду ) Начали мы с SST где один стек и один поток на всех в системе давайте и будем продолжать в том-же духе. А не гордиться тем как здорово можно с помощью ядра ОС поставить в очередь IO операцию и подписаться на события (говоря языком JS) от этой операции. Правильно уже вам тут говорили, вы даже реализацию TCP/IP со своим SST запаритесь делать.
--------------------
The truth is out there...
|
|
|
|
|
Sep 9 2016, 07:47
|
Профессионал
    
Группа: Свой
Сообщений: 1 047
Регистрация: 2-12-06
Из: Kyiv, Ukraine
Пользователь №: 23 046

|
Цитата Очень бы хотелось увидеть как бы вы обслуживали много клиентов, рисовали GUI и делали еще то что нужно и именно на голой системе и именно со своим SST и чтоб в масштабах ВСЕЙ системы был бы один стек и один поток. Собственно это то, с чего мы начинали нашу дискуссию. Так я именно это и постоянно делаю! У меня GUI, сложные вычисления, куча IO(флешки,ацп,датчики 1wire) отлично работают на SST без единых тормозов и проблем. На многопоточной оси аля FreeRTOS постоянно были проблемы, то гонки, то оверхед большой, то еще что-то. Цитата И для того чтобы организовать то-же самое вам придется реализовать примерно такие-же механизмы в своей системе. Ну так они реализованы - это SST  При чем довольно просто, сам SST - это около 300 строк кода, динамическая очередь - почти 200 строк кода, очередь задач - 60 строк кода, делегат(Вы уже его видели выше) - 80 строк кода Цитата Правильно уже вам тут говорили, вы даже реализацию TCP/IP со своим SST запаритесь делать. Сделана и отлично работает. Цитата Начали мы с SST где один стек и один поток на всех в системе давайте и будем продолжать в том-же духе. Ну так я показал, как на своем SST я работаю с DataFlash - привел полный код операции ее стирания. Что там непонятного? Могу любую другую задачу показать как я делаю ее на SST. Говорите какую - приведу код, тк у меня практически все задачи уже решены в виде простейших библиотек и я их использую когда надо - строю программу из этих кубиков. Цитата(Serhiy_UA @ Sep 9 2016, 10:23)  В моем случае под 7 версией FreeBCD на индустриальном компьютере в режиме реального времени одновременно работают пять равно приоритетных программ. Каждая через new/delete (без перегрузок) создает для себя динамические объекты в куче. Если куча общая, то ОС должна четко организовывать управление кучей для всех программ. Хотел узнать (прочесть) об этом немного больше, хотя и так кое-что уже прояснилось. Ну в таких сложных ОС, как FreeBSD управление кучей очень сложное. Куча для каждого процесса своя, а для потоков внутри этого процесса общая, и ессно ядру нужно ею управлять так, чтобы это не сказывалось на других процессах, там все довольно сложно и вникать туда смысла нет - жизни не хватит. Зато есть смысл делать динамику на embedded, где и памяти мало, и других ресурсов, но чувствовать себя там практически так, как будто под PC пишешь. В FreeBSD там все довольно грамотно да и работать в асинхронном стиле(без ожиданий и циклов) там гораздо проще, чем в линуксе. В линуксе до сих пор нет нормальных асинхронных IO, есть костыли вроде epoll. Тогда как в FreeBSD есть Kqueue. Думаю, если мой тест запустить на FreeBSD, то NodeJS еще сильнее убьет в хлам классические pthread. А тем временем провел другой тест - сервер выдает не файл(который ОС выдает из кеша, а не читает по факту с диска) а рандомные данные по таймеру по 100 байт каждые 10 милисекунд вечно, пока клиент не отключится. Вот так это выглядит на NodeJS, в однопоточном асинхронном стиле: Код var timer; function on_socket_closed(){ n_connections--; clearInterval(timer); }
// Periodically send random data to client timer = setInterval(function(){ var str; for(var i=0; i<100; i++){ str+= String.fromCharCode(Math.random()*256); } socket.write(str); }, 10); То есть для каждого клиента мы создаем таймер, в обработчике события которого генерируется рандом и потом ставится в очередь на отправку клиенту. Не шибко эффективный способ,тк время генерации рандома велико - тк это мы делаем синхронным блокирующим способом - в цикле for. правильно было бы прoчитать эти данные из готового асинхронного рандом-генератора (типа /dev/urandom) и передать клиенту - моментально, одной строчкой. Но тк я буду делать то же самое на С, только в многопоточном стиле - специально сделал так, чтобы сразу не убить в хлам многопоточную модель  И того такой сервер на моем ноуте может обслуживать 3900 клиентов одновременно и занимает при этом 80МБ оперативки. При чем клиент запущен на том же ноуте, асинхронный тот же, что и прежний, на JS. Сейчас наклепаю на C в мнгопоточном блокирующем стиле.
|
|
|
|
|
Sep 9 2016, 07:57
|
Профессионал
    
Группа: Свой
Сообщений: 1 047
Регистрация: 2-12-06
Из: Kyiv, Ukraine
Пользователь №: 23 046

|
Так я уже показал, как я работаю с Датафлеш. Привел полный код операции стирания http://electronix.ru/forum/index.php?showt...t&p=1447897Хотите на код самой SPI посмотреть? Запросто, сейчас выложу Вот CODE #include <drv/LPC17xx/ssp.hpp> #include <kernel-sst/taskqueue.hpp>
class SspTaskQueue : public TaskQueue<48>{ public: SspTaskQueue(Ssp* ssp, SST::TPriority priority): TaskQueue(priority), ssp(ssp) {}
// proxy, use inside tasks int dmaChannelRx()const{ return ssp->dmaChannelRx(); } int dmaChannelTx()const{ return ssp->dmaChannelTx(); }
bool DmaWriteRead(const void *src, void *dst, int len){ return ssp->DmaWriteRead(src,dst,len); }
void setup(uint8_t size, bool cpol, bool cpha, uint16_t clkdiv){ return ssp->setup(size, cpol, cpha, clkdiv); }
void flush()const{ return ssp->flush(); }
private: Ssp *ssp; }; Или мало? Тогда вот CODE #include "dma.hpp"
class Ssp{ public: Ssp(int port); // DMA int dmaChannelRx()const{ return dma_channel_rx; } int dmaChannelTx()const{ return dma_channel_tx; }
bool DmaWriteRead(const void *src, void *dst, int len){ if(Dma::isChannelsBusy(dma_channel_rx,dma_channel_tx))return false; start_dma_read(dst,len); start_dma_write(src,len); return true; }
// Дальше можно не смотреть, это чисто платформо-зависимый код private: void start_dma_write(const void *src, int len){ Dma::startChannel(dma_channel_tx, src, (void*)&ssp->DR, 0, len, DMACCControl_xBSize_1, DMACCControl_xBSize_1, DMACCControl_DWidth_8, DMACCControl_DWidth_8, true, false, static_cast<DMA_Peripheral>(0), static_cast<DMA_Peripheral>(dma_peripheral_tx), DMA_TransferType_Mem2Periph ); }
void start_dma_read(void *dst, int len){ Dma::startChannel(dma_channel_rx, (void*)&ssp->DR, dst, 0, len, DMACCControl_xBSize_1, DMACCControl_xBSize_1, DMACCControl_DWidth_8, DMACCControl_DWidth_8, false, true, static_cast<DMA_Peripheral>(dma_peripheral_rx), static_cast<DMA_Peripheral>(0), DMA_TransferType_Periph2Mem ); }
private: LPC_SSP_TypeDef *ssp; int8_t dma_channel_rx; int8_t dma_channel_tx; uint8_t dma_peripheral_rx; uint8_t dma_peripheral_tx; uint8_t dummy_ff;
Хватит? Или еще dma кинуть? Там кроме платформозависимого больше ничего нет. Цитата Ваш пример не работает в один поток потому что ядро работает параллельно с вами, в классическом потоке, со своим стеком и шлет вам в юзерспэйс приветы(ивэнты) с глубины ring0 sm.gif Кто мешает работать без этого ядра, потоков и ring0, на простом МК вроде Атмега8 и получить точно такое же поведение программы, как в NodeJS ? Ведь для программы потоки не нужны, они нужны(а нужны ли?) конкретной оси(линуксу) для планировки программы.
|
|
|
|
|
Sep 9 2016, 07:57
|

unexpected token
   
Группа: Свой
Сообщений: 899
Регистрация: 31-08-06
Из: Мехелен, Брюссель
Пользователь №: 19 987

|
Цитата(sigmaN @ Sep 9 2016, 09:52)  Всё всё, хватит, мы уже поняли что на Node.js можно в две строки запустить сервер и он будет быстро обрабатывать большое кол-во клиентов. Покажите уже как всё это реализуется когда во всей системе один поток и один стек для всех ) Собсвтенно выше я уже написал всё тоже самое) Ваш пример не работает в один поток потому что ядро работает параллельно с вами, в классическом потоке и шлет вам вюзерспэйс приветы(ивэнты) с глубины ring0  Вероятно, есть критерии, когда SST уместна, а когда лучше применять многопоточный вариант.
--------------------
А у тебя SQUID, и значит, мы умрем.
|
|
|
|
|
Sep 9 2016, 08:00
|
Профессионал
    
Группа: Свой
Сообщений: 1 047
Регистрация: 2-12-06
Из: Kyiv, Ukraine
Пользователь №: 23 046

|
Цитата(alexunder @ Sep 9 2016, 10:57)  Вероятно, есть критерии, когда STT уместна, а когда лучше применять многопоточный вариант. Естественно есть. Например пользовательские ОС, где пользователь может запускать свои или чужие приложения. И то, в нормальных ОС, типа FreeBSD для каждого процесса есть по сути свой SST - это kqueue. То есть приложения планируются в многопоточном стиле, а работу внутри приложений можно делать в однопоточном асинхронном, с приоритетами - вобщем полный SST. В линуксе (по крайней мере на сколько мне известно) есть костыль в виде epoll, через который и реализовано все в таких штуках, как Nginx или NodeJS. NodeJS - это не SST, у него один приоритет, а у SST их много. Если в NodeJS где либо втулить тяжелый или вечный цикл - вся программа умрет, а если это сделать в SST - то будет продолжать работать, умрут только менее приоритетные задачи. SST - это продолжение NodeJS.
|
|
|
|
|
Sep 9 2016, 08:26
|
Профессионал
    
Группа: Свой
Сообщений: 1 047
Регистрация: 2-12-06
Из: Kyiv, Ukraine
Пользователь №: 23 046

|
Сделал сервер сишный блокирующий многопоточний. Как и предполагалось, система умерла сразу, как только к серверу подключился nodejs клиент  Так что это миф, когда говорят, что в многопоточной ОС криво написанное приложение не сможет повлиять на другие - очень даже может, и не просто повлиять, а завалить всю ОС намертво. Еще один плюс в пользу асинхронщины  Сколько памяти заняло - не знаю, тк система умерла, пришлось жать reset, но думаю, что всю. Что успел заметить - сервак успел обработать 1160 коннектов(это 1162 потока - 1160 клиентских + вотчер + главный поток, принимающий коннекты) Код char buffer[4096]; int i,r; while(1){ for(i=0; i<100; i++)buffer[i] = rand(); r = send(sock, buffer, 100, 0); if(r<=0){ perror("Thread_connection: socket error"); break; } usleep(10000); }; Так что однопоточные приложения круче и проще многопоточных, даже на многопоточных осях, не говоря уже про чисто асинхронные, типа SST  Цитата(alexunder @ Sep 9 2016, 11:16)  Нет ли у Вас ссылок на какую-нибудь литературу по SST (необязательно для embedded)? Да там то и ссылок особо быть не может. А то, что есть - в этом топике приводили, в том числе и на русском. Тут нужно научиться мыслить асинхронно, без блокировок - перестроить свой мозг. Привыкнуть применять всякие замыкания, колбеки. А затем SST само собой получится. Для тренировки отлично подходит замечательный инструмент - NodeJS, язык яваскрипт - синтаксис у него сишный, для C-программиста проблем не будет. Для работи на МК в асинхронном стиле вообще ничего левого не нужно - нужен голый C++ и умение программировать. А тот, кто будет дергать ивенты там уже есть - это контроллер прерываний. Какие ще на МК могут быть ивенты, кроме прерываний? В асинхронном программировании нужно стараться не использовать циклов вообще. А если использовать, то очень короткие. Если вы видите, что у вас длинный цикл(больше, чем скажем 8 шагов для МК и около 100 для PC) - значит уже делаете что-то не так, особенно, если это операции ввода/вывода (тот же printf). В асинхронщине рулит рекурсия, а это уже шаги в сторону функционального программирования. Речь идет не о сложных математических задачах, а о программировании в целом. Конечно, если умножение матриц или какой-нибудь криптографический алгоритм требует 100 000 циклов, то трогать их не нужно. Просто эту операцию нужно выполнять на другом уровне приоритета, чтобы не мешало остальным(ІО, GUI).
|
|
|
|
|
Sep 9 2016, 09:36
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Почерпнул много интересного из этого обсуждения. Прямо как будто зашел на электроникс лет 8 назад. brag, не могли бы вы прокомментировать этот кусочек кода: CODE template<class T, int d_size=1> class delegate;
template<class TRet, class... TArgs, int d_size> class delegate<TRet(TArgs...),d_size> { public: Как так? Сначала предварительно объявляем шаблон с двумя параметрами, а потом уже полное объявление с другим количеством? На всякий случай каюсь, до изучения шаблонов с переменным числом параметров я пока не дошел...
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|