Проверьте, все ли элементы равны с C++17 fold-выражением
У меня есть функция, принимающая пакет с переменными параметрами, и в начале я хочу проверить, чтобы все элементы сравнивались одинаково. Могу ли я каким-то образом использовать новые C++17 складные выражения, чтобы кратко написать это как однострочную строку? я думал
template<typename... Args>
void func (Args... args)
{
ASSERT ((args == ...));
// more code here...
}
но это не работает, так как он компилируется в код, который сначала правильно сравнивает последние два аргумента, но затем сравнивает третий-последний аргумент с результатом первого сравнения, которое является логическим. Какой вариант использования может иметь этот тип выражения сгиба (аналогично args < ...
)? Есть ли шанс, что я смогу избежать написания специального рекурсивного шаблона для этого?
3 ответа
К сожалению, причина, по которой это не работает, заключается в том, что булевы операторы не связываются в C++, как в других языках. Итак, выражение:
a == (b == c)
(то, что расширило бы ваше выражение сгиба), сравнило бы a
либо true
или же false
, не при чем b
или же c
на самом деле Я надеялся operator<=>
добавил бы цепочку, но, очевидно, эта часть была отброшена.
Исправления в том, что вы должны разбить сравнения:
(a == b) && (b == c)
Конечно, это не очень хорошо складывается, но вместо этого вы можете сравнить все с первым элементом:
(a == b) && (a == c)
Что сказать:
((a0 == args) && ... )
На этом этапе нам просто нужно вытащить первый элемент. Нет проблем, это, очевидно, для чего лямбды:
template <class... Args>
bool all_equal(Args const&... args) {
if constexpr (sizeof...(Args) == 0) {
return true;
} else {
return [](auto const& a0, auto const&... rest){
return ((a0 == rest) && ...);
}(args...);
}
}
Как предложил Петр Скотницкий, простое решение отделяет первый аргумент от следующих и проверяет его с помощью &&
как оператор сгиба
К примеру, следующая функция, которая возвращает true
если все аргументы равны
template <typename A0, typename ... Args>
bool foo (A0 const & a0, Args const & ... args)
{ return ( (args == a0) && ... && true ); }
К сожалению, это не может работать с пустым списком аргументов.
std::cout << foo(1, 1, 1, 1) << std::endl; // print 1
std::cout << foo(1, 1, 2, 1) << std::endl; // print 0
std::cout << foo() << std::endl; // compilation error
но вы можете добавить специальный пустой аргумент foo()
bool foo ()
{ return true; }
Если по какой-то причине вы не можете разделить args
в a0
и следующее args
?
Ну... очевидно, вы можете использовать предыдущее foo()
функция (со специальной пустой версией)
template<typename... Args>
void func (Args... args)
{
ASSERT (foo(args));
// more code here...
}
или вы можете использовать C++17-кратное выражение с запятой и присваиванием, как показано ниже bar()
template <typename ... Args>
bool bar (Args const & ... args)
{
auto a0 = ( (0, ..., args) );
return ( (args == a0) && ... && true );
}
Соблюдайте начальный ноль в a0
присваивание, разрешающее использование этого решения также с пустым списком аргументов.
К сожалению, из предыдущего auto a0
присваивание Я получаю много предупреждений ("результат выражения не используется" от clang++ и "левый операнд оператора запятой не имеет никакого эффекта" от g++), которых я не знаю, как избежать.
Ниже приводится полный рабочий пример
#include <iostream>
template <typename A0, typename ... Args>
bool foo (A0 const & a0, Args const & ... args)
{ return ( (args == a0) && ... && true ); }
bool foo ()
{ return true; }
template <typename ... Args>
bool bar (Args const & ... args)
{
auto a0 = ( (0, ..., args) );
return ( (args == a0) && ... && true );
}
int main ()
{
std::cout << foo(1, 1, 1, 1) << std::endl; // print 1
std::cout << foo(1, 1, 2, 1) << std::endl; // print 0
std::cout << foo() << std::endl; // print 1 (compilation error
// witout no argument
// version)
std::cout << bar(1, 1, 1, 1) << std::endl; // print 1
std::cout << bar(1, 1, 2, 1) << std::endl; // print 0
std::cout << bar() << std::endl; // print 1 (no special version)
}
-- РЕДАКТИРОВАТЬ --
Как указано dfri (спасибо!), За и пусто args...
pack, значения для следующих сложенных выражений
( (args == a0) && ... )
( (args == a0) || ... )
соответственно true
а также false
,
Так что верните инструкцию foo()
а также bar()
может быть безразлично написано
return ( (args == a0) && ... && true );
или же
return ( (args == a0) && ... );
и это верно также в случае sizeof...(args) == 0U
,
Но я склонен забывать такого рода детали и предпочитаю явные (с окончательным && true
) значение в пустом регистре.
Вот как я это делаю в библиотеке gcl :
template <auto ... values>
constexpr static auto equal_v = []() consteval {
static_assert(sizeof...(values) > 0, "gcl::mp::value_traits::equal_v : no arguments");
constexpr auto first_value = std::get<0>(std::tuple{values...});
static_assert(
(std::equality_comparable_with<decltype(values), decltype(first_value)> && ...),
"gcl::mp::value_traits::equal_v : cannot compare values");
return ((values == first_value) && ...);
}();
или замена
static_assert
по требованию концепции:
template <typename ... Ts>
concept are_equality_comparable = requires(Ts ... values)
{
{
std::conditional_t<(std::equality_comparable_with<decltype(std::get<0>(std::tuple{values...})), decltype(values)> && ...), std::true_type, std::false_type>{}
} -> std::same_as<std::true_type>;
};
template <auto ... values>
requires(are_equality_comparable<decltype(values)...>)
constexpr static auto equal_v = []() consteval {
static_assert(sizeof...(values) > 0, "gcl::mp::value_traits::equal_v : no arguments");
constexpr auto first_value = std::get<0>(std::tuple{values...});
return ((values == first_value) && ...);
}();