Перейти к содержанию
    

Плавный переход C -> C++ под МК

Запутался с variadic templates. Мутные они какие-то.

Хочу заиметь 2 функции: sendCommand() и sendData(). Они на 99.99% идентичны друг другу, и хотелось бы, чтобы в их телах производился вызов другой "общей" функции с правильно подсунутыми параметрами (возможно, не шаблонными).

template<args>
void send(int dataCommand, args) {
  // тут уже надо распарсить, сколько аргументов реально, какие их типы и т.д.
}

template<...>
void sendCommand(...) {
  send(0, ...);
}

template<...>
void sendData(...) {
  send(1, ...);
}


В конечном итоге хочу получить возможность вот такой записи

// аргументы - простые целые числа
sendCommand(0x12);             // должно вызваться send(0, 0x12)
sendCommand(0x12, 0x34);       // должно вызваться send(0, 0x12, 0x34)
sendCommand(0x12, 0x34, 0x56); // должно вызваться send(0, 0x12, 0x34, 0x56)

sendData(0x12, 0x34);          // должно вызваться send(1, 0x12, 0x34)

// аргументы - массив или строка
sendCommand("hello");          // должно вызваться send(0, const char (&)[6])

u8 buf[3];
sendData(buf);                 // должно вызваться send(1, u8 (&)[3])


Т.е. могу вызывать перегрузку с целыми аргументами в количестве от 1 до 3, а могу запихивать прямо массив или строку - для этого передача идет по "ссылке на массив", чтобы в send() можно было взять N или sizeof(buf).

На вариативных шаблонах витиевато, не знаю - вообще возможно ли это.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

14 minutes ago, Arlleex said:

Хочу заиметь 2 функции: sendCommand() и sendData(). Они на 99.99% идентичны друг другу, и хотелось бы, чтобы в их телах производился вызов другой "общей" функции с правильно подсунутыми параметрами (возможно, не шаблонными).

а без шаблонов никак?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

50 минут назад, Forger сказал:

а без шаблонов никак?

Как? Перегружать sendCommand и sendData множество раз? Нет, не красиво. Куча одного и того же кода.

А я хотел "прокинуть шаблон в шаблон" - т.е. сделать перегрузку только send(), а sendCommand и sendData сделать универсальными.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

21 minutes ago, Arlleex said:

Перегружать sendCommand и sendData множество раз?

зачем их перегружать?

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

приватные функции дать ему через префикс protected, чтобы могли обращаться наследники

перегрузка - это не есть хорошо, кмк

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

// Основная шаблонная функция 
send template<typename... Args> 
  void send(int dataCommand, Args&&... args) 
{ 
  if (dataCommand == 0) 
  { 
    std::cout << "sendCommand called with arguments: "; 
  } 
  else 
  { 
    std::cout << "sendData called with arguments: "; 
  } 
  (std::cout << ... << args) << std::endl; 
} 
// Обертка для sendCommand 
template<typename... Args> 
void sendCommand(Args&&... args) 
{ 
  send(0, std::forward<Args>(args)...); 
} 
// Обертка для sendData 
template<typename... Args> 
  void sendData(Args&&... args) 
{ 
  send(1, std::forward<Args>(args)...); 
} 

 

int main() 
{ 
  // Примеры использования 
  sendCommand(0x12); // вызовется send(0, 0x12) 
  sendCommand(0x12, 0x34); // вызовется send(0, 0x12, 0x34) 
  sendCommand(0x12, 0x34, 0x56); // вызовется send(0, 0x12, 0x34, 0x56) 
  sendData(0x12, 0x34); // вызовется send(1, 0x12, 0x34) 
  
  // Примеры с массивами и строками 
  sendCommand("hello"); // вызовется send(0, "hello") 
  unsigned char buf[3] = {0x01, 0x02, 0x03}; 
  sendData(buf); // вызовется send(1, buf) 
  return 0; 
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, Forger сказал:

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

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

Какой толк что-то наследовать и писать тут классы, как это поможет решению именно хотелки с переменным числом аргументов и их типами при вызове функций?

@juvf а std::forward() здесь обязателен? Т.е. можно ли обойтись без обращения к std вовсе?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Поздравляю, что познали "радость" вариативных шаблонов - это еще один недопиленный и откровенно хреновый инструмент С++. Говорят, скоро его должны малость подштопать.

По сути вопроса: я полагаю, конкретно здесь можно обойтись и без шаблонов, написав перегрузки sendCommans для разного числа параметров. Делать именно шаблон тут как бы не очень стоит, потому как шаблон создает именно дубликаты, что увеличивает размер прошивки. 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

50 minutes ago, Arlleex said:

как это поможет решению именно хотелки с переменным числом аргументов и их типами при вызове функций?

а надо "шашечки или ехать"? 😉

так ли нужно тут это переменное число аргументов? универсальность ради универсальности?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 часов назад, Arlleex сказал:

В конечном итоге хочу получить возможность вот такой записи

Так?

void send(uint8_t id, void const * from, size_t size);

void send_command(void const * from, size_t size)       { send(0, from, size); }

template <typename T>
void send_command(T const & what)                       { send_command(&what, sizeof(T)); }

template <typename T, size_t items>
void send_command(T const (&what)[items])               { send_command(&what, sizeof(T) * items); }

template<size_t length>
void send_command(char (&string)[length])               { send_command(&what, size - 1); }


void test()
{
  send_command(0x12);
  send_command({0x12, 0x34, 0x56});
  send_command("Hello");
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 час назад, Forger сказал:

а надо "шашечки или ехать"? 😉

так ли нужно тут это переменное число аргументов? универсальность ради универсальности?

Не знаю, просто пока что лично для меня и моей логики кажется очень логичным сделать именно с переменным числом аргументов и их типами🙂

Но я хотел и без нагромождения. Поэтому наследования от наследования от трижды абстрактных классов - нет нет нет))

31 минуту назад, Сергей Борщ сказал:

Так?

Почти да! Только у Вас "хитрость" - по сути все send_command имеют тип аргументов "массив" или "что-то, что можно упаковать в массив".

Однако, когда я перечисляю аргументы через запятую отдельно - у меня должна в теле send() сработать другая логика отправки.

Т.е. передача массивов - отлично! Но send_command(1, 2), например, - уже ошибка.

Как это видел я: есть некий шаблон для send_command(). Он одного вида, т.е. нет множества определений шаблонов send_command(). А вот он, в зависимости от того, что ему напихали в реальной точке вызова, подставляет одну из разновидностей send() в зависимости от контекста.

 

Поясню что хочу сделать. Есть экранчик. Подключен по SPI. Хочу чтобы из драйвера экранчика наружу торчали только 2 сигнатуры функций:

send_command(...);
send_data(...);

Отличаются функции только установкой аппаратной ножки дисплея в 0 или 1. Дальше отправка - одинаковая.

Однако самих форматов аргументов этих функций (данных для отправки) должно быть ограниченное множество.

Драйвер должен понимать следующие записи на примере send_command():
1. send_command(addr, size); - это для случая, когда например, в памяти (ОЗУ или Flash) лежит битмап и я напрямую заряжаю отправку из памяти.
2. send_command(byte1) / send_command(byte1, byte2) / send_command(byte1, byte2, byte3); - это особый случай, когда у меня данные лежат не в памяти, а в аргументах вызова, поэтому я внутри таких перегруженных реализаций должен чуть по-другому работать с "ядром" драйвера отправки.
3. send_command("string") / send_command(buf); - отправка явных массивов, размер должен автоматом посчитаться и подсунуться в вызов send_command() из п. 1.

У меня хитрый механизм отправки, который анализирует, в памяти ли лежит кусок данных (если да, то в какой именно - ОЗУ/Flash), или в другом месте - и это должно обрабатываться по-разному.

Вот поэтому я хотел, чтобы наружу торчали только send_command() и send_data() по 1 штуке, шаблонизированных.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Меня практически полностью устраивает финальный вид функций из примера @juvf.

Однако, у него основная send() - одна. И внутри ее не понять, вызвали ее в виде send_command(1, 2, 3) или в виде send_command("hello").

Было бы здорово, если можно было бы перегрузить реализации send() под 2 варианта: аргумент-массив, и аргумент-список (параметры через запятую). В обоих случаях в теле send() я должен знать размер массива или количество переданных аргументов.

P.S. О! В send() можно с помощью sizeof...(args) узнать количество аргументов!!! Бинго! Все эти точечки в разных местах очень жутко выглядят)) Может кто подскажет адекватную статейку или ссылку "для чайников" по вариативным шаблонам? А то глянул на том же хабре - первый абзац пишется для чайников, следующий уже для трушных плюсоводов😀

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так и думал, что дисплей. С дисплеем лучше все эти байты команд упрятывать внутрь функций с осмысленными именами. Например,

	static bool SetColumn(uint16_t start, uint16_t end)
	{
		if(SPI::GetBusy())
			return false;
		CS::Low();				// CS -\_
		DC::Low();				// DC = 0
		SPI::Write(0x2A);			// CASET
		WaitComplete();
		DC::High();				// DC = 1
		SPI::Write(start >> 8);
		SPI::Write(start);
		SPI::Write(end >> 8);
		SPI::Write(end);
		WaitComplete();
		CS::High();				// CS _/-
		return true;
	}

где все эти неинформативные коды команд и параметров спрятаны внутри. Таким образом, вы видите имя функции с понятными входными параметрами - функция установки границ столбцов с номерами начального и конечного столбца. И вам не надо знать кодов команд и того, в какой последовательности идут параметры команды.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 минуты назад, EdgeAligned сказал:

Так и думал, что дисплей. С дисплеем лучше все эти байты команд упрятывать внутрь функций с осмысленными именами. Например,

Это не противоречит моей задумке. Мухи - отдельно - котлеты - тоже.

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А полюбасу, многие дисплеи имеют систему команд с переключением DC при записи кода команды и параметров команды, поэтому полюбасу придется управляться с DC через ожидание полной передачи байта. Во-вторых, SPI у дисплея обычно составляет 5-10 МГц, и передача по прерываниям тут малоэффективна, а DMA для команд невозможен по указанной выше причине переключения DC. Поэтому, за исключением небольшого числа дисплеев типа SSD1306, у которого DC не изменяется в командном режиме, в большинстве дисплеев нет смысла в "неблокирующих" передачах команд.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 минут назад, EdgeAligned сказал:

А полюбасу, многие дисплеи имеют систему команд с переключением DC при записи кода команды и параметров команды, поэтому полюбасу придется управляться с DC через ожидание полной передачи байта. Во-вторых, SPI у дисплея обычно составляет 5-10 МГц, и передача по прерываниям тут малоэффективна, а DMA для команд невозможен по указанной выше причине переключения DC. Поэтому, за исключением небольшого числа дисплеев типа SSD1306, у которого DC не изменяется в командном режиме, в большинстве дисплеев нет смысла в "неблокирующих" передачах команд.

У меня логика драйвера такая: вызвали send(). Если буферизатор пуст, т.е. никаких транзакций нет - ножка DC управляется как надо - и заряжается DMA на отправку данных. Если буферизатор не пуст (прошлые отправки не завершены), то данные (будь то адрес памяти + размер или прямо сами аргументы - для коротких команд) помещаются в дескриптор. Дескрипторы кладутся в кольцевой буфер. По завершению транзакции DMA вычитывает из буферизатора очередной дескриптор и выполняет то, что в нем записано: поднять DC куда надо и отправить N байт. В драйвере еще кое-что реализовано, но сейчас это не важно. Важно то, что можно писать линейный код send() send() send()... и не забивать себе голову ожиданием отправки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...