#c #c 14 #c 17 #variadic-templates #fold-expression
#c #c 14 #c 17 #variadic-шаблоны #fold-выражение
Вопрос:
Я новичок в шаблонах переменных, но мне все же удалось запрограммировать некоторый код на c 11, используя его, но я все еще чувствую недовольство результатом, потому что ему не хватает выразительности.
Проблема заключается в реализации функции, которая принимает несколько bool
условий (от 1
до любого) и возвращает целочисленный код, указывающий, в каком месте находится первый false
аргумент «», или 0
если все они есть true
.
e.g. "error_code(true, true, true);" must return 0
e.g. "error_code(true, true, false);" must return 3
e.g. "error_code(true, false, false);" must return 2
e.g. "error_code(false, false, false);" must return 1
Мой текущий код расшифровывается как (прямая ссылка на coliru: http://coliru.stacked-crooked.com/a/1b557f2819ae9775 ):
#include <tuple>
#include <iostream>
int error_impl(bool cond)
{
return cond;
}
template<typename... Args>
int error_impl(bool cond, Args... args)
{
return cond ? 1 error_impl(args...) : 0;
}
template<typename... Args>
int error_code(Args... args)
{
constexpr std::size_t size = std::tuple_size<std::tuple<Args...>>::value 1;
return (error_impl(args...) 1) % size;
}
int main()
{
auto e1 = error_code(true, true, true);
auto e2 = error_code(true, true, false);
auto e3 = error_code(true, false, false);
auto e4 = error_code(false, false, false);
std::cout << std::boolalpha;
std::cout << "(true, true, true)==0 -> " << (e1 == 0) << "n";
std::cout << "(true, true, false)==3 -> " << (e2 == 3) << "n";
std::cout << "(true, false, false)==2 -> " << (e3 == 2)<< "n";
std::cout << "(false, false, false)==1 -> " << (e4 == 1)<< "n";
auto e5 = error_code(true, true, true, true);
auto e6 = error_code(true, true, true, false);
auto e7 = error_code(true, true, false, false);
auto e8 = error_code(true, false, false, false);
auto e9 = error_code(false, false, false, false);
std::cout << "(true, true, true, true)==0 -> " << (e5 == 0) << "n";
std::cout << "(true, true, true, false)==4 -> " << (e6 == 4) << "n";
std::cout << "(true, true, false, false)==3 -> " << (e7 == 3) << "n";
std::cout << "(true, false, false, false)==2 -> " << (e8 == 2)<< "n";
std::cout << "(false, false, false, false)==1 -> " << (e9 == 1)<< "n";
}
Интересно, где эту error_code()
функцию «» можно улучшить, используя новые функции развертывания из c 14 / c 17, чтобы она улучшала выразительность и использовала менее 3 функций.
Любая помощь будет признательна!
Ответ №1:
C 17 со сворачиванием:
template<class... Bools>
constexpr unsigned error_code(Bools... Bs) {
unsigned rv = 1;
(void) ((rv = Bs, !Bs) || ...);
return rv % (sizeof...(Bs) 1);
}
Незапрошенный, так что это просто бонус — та же идея, C 20:
constexpr unsigned error_code(auto... Bs) {
unsigned rv = 1;
(void) ((rv = Bs, !Bs) || ...);
return rv % (sizeof...(Bs) 1);
}
Объяснение:
- Первая часть выражения fold содержит две части , разделенные символом a
,
. Результат левой части отбрасывается, и результатом такого выражения является крайняя правая часть,!Bs
.(rv = Bs, !Bs)
- Вторая часть
|| ...
— это то, где происходит складывание (или раскладывание). Первое выражение копируется / вставляется повторно до тех пор, пока в пакете не останется больше аргументов. Ибоtrue, false, true
это становится:(rv = 1, !true) || (rv = 0, !false) || (rv = 1, !true)
или
(rv = 1, false) || (rv = 0, true) || (rv = 1, false)
- Начинается оценка короткого замыкания. Когда встроенный оператор 1
||
имеет atrue
с левой стороны, правая сторона не вычисляется. Вот почему в этом примере выполняется только один изrv = 1
«s».(rv = 0, true)
Останавливает оценку, поэтому оценивается только это:(rv = 1, false) || (rv = 0, true)
- Последнее
rv % (sizeof...(Bs) 1);
— позаботиться о случае , когдаfalse
значения не найдены и мы должны вернуться0
. Пример:unsigned rv = 1; (rv = 1, !true) || (rv = 1, !true) || (rv = 1, !true); // rv is now 4, and sizeof...(Bs) == 3, so: 4 % (3 1) == 0
- Почему
(void)
?
Компиляторы любят предупреждать о том, что они считают неиспользуемыми выражениями. Тщательно размещенный(void)
символ говорит ему, что нам все равно, поэтому он заставляет компилятор молчать.
1 — Это не относится к определяемым пользователем операторам.
Комментарии:
1. извините, я по ошибке удалил свой последний комментарий. Я пишу это снова: большое вам спасибо за ваш ответ! Мне потребуется некоторое время, чтобы по-настоящему разобраться в коде, но теперь у меня есть четкий код, чтобы продолжать совершенствовать свои (пока немногочисленные) навыки работы с вариационными шаблонами. Кроме того, я хотел бы поблагодарить вас за ваш раздел c 20. Как разработчик, я с нетерпением жду начала работы с c 20 и его новыми функциями, особенно диапазонами, поэтому приветствуется любое новое обучение.
2. Кстати, я не знаю конструкцию «sizeof … (Bs)», которая позволяет избавиться от ужасного взлома кортежей, который я использовал для вычисления количества аргументов в функции.
3. @Пабло
sizeof...
действительно хороший! Я редко использую его в повседневной жизни 🙂4. @max66 Спасибо тебе! Мне твой тоже понравился! Мне потребовалось достаточно времени, чтобы создать решение на C 17, и я слишком устал, чтобы перезагрузить свой мозг, чтобы создать версию на C 14, как это сделали вы. Тем не менее, я застрял с C 14 на работе 🙂
5. @TedLyngmo — Вы правы; извините: я только перенес выражение сгиба. Забудь об этом.
Ответ №2:
Как насчет (C 17) следующим образом?
template <typename... Args>
int error_code (Args... args)
{
int ret = 0;
int val = 1;
( (args || ret ? 0 : ret = val, val), ... );
return ret;
}
В C 11 и C 14 требуется немного (немного!) больше машинописи.
template <typename... Args>
int error_code (Args... args)
{
using unused = int[];
int ret = 0;
int val = 1;
(void)unused{ 0, (args || ret ? 0 : ret = val, val)... };
return ret;
}
Комментарии:
1. Большое вам спасибо за ваш ответ! Мне потребуется некоторое время и целый сеанс отладчика, чтобы понять, как это работает, но, по крайней мере, теперь у меня есть надежный код, позволяющий углубляться в шаблоны переменных.
Ответ №3:
Поскольку вы знаете, что все ваши аргументы будут преобразованы в a bool
, лучше вообще не использовать переменные аргументы:
inline int error_code(std::initializer_list<bool> args) {
int index = std::find(args.begin(), args.end(), false) - args.begin();
if (index == args.size()) return 0;
return 1 index;
}
// Either directly call the above `error_code({ true, true, false, ... })`
// Or if you must have a parameter pack
template<typename... Args>
int error_code(Args... args) {
std::initializer_list<bool> v{ args... };
int index = std::find(v.begin(), v.end(), false) - v.begin();
if (index == sizeof...(args)) return 0;
return index 1;
// If you have both functions, simply have: return error_code({ args... });
}
Компилятор, похоже, оптимизирует его аналогично вашему вариантному решению (и оно даже работает в C 11).
Вот более интересное решение, которое использует C 17-кратные выражения:
template<typename... Args, int... I>
int error_code_impl(Args... args, std::integer_sequence<int, I...>) {
int result = 0;
([amp;result](bool arg){
if (!arg) result = I 1;
return arg;
}(args) amp;amp; ...);
// Can also be written without the lambda as something like:
// ((args ? true : ((result = I 1), false)) amp;amp; ...);
return resu<
}
template<typename... Args>
int error_code(Args... args) {
std::make_integer_sequence<int, sizeof...(args)> indices;
return error_code_impl<Args...>(indices, args...);
}
Комментарии:
1. Действительно круто! На первый взгляд, я почти поддался искушению использовать переменные функции из старого доброго простого языка C. Ваше решение запомнило меня, но ваше решение улучшается с использованием «std::initializer_list» вместо ключевых слов va_start, va_end … жутко. Мне очень нравится ваше первое решение, до сих пор оно было наилучшим для чтения, и его легко понять любому