Проверьте, все ли элементы равны с 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) && ...); 
}();
Другие вопросы по тегам