使用 C++17 折叠表达式测试所有元素是否相等
Test if all elements are equal with C++17 fold-expression
我有一个采用可变参数包的函数,一开始我想检查所有元素是否相等。我能否以某种方式使用新的 C++17 折叠表达式将其简洁地写成一行?我在想
template<typename... Args>
void func (Args... args)
{
ASSERT ((args == ...));
// more code here...
}
但这不起作用,因为它编译为代码,首先正确比较最后两个参数,然后将倒数第三个参数与第一次比较的结果进行比较,这是一个布尔值。这种类型的折叠表达式可能有什么用例(类似于 args < ...
)?我是否有机会避免编写专用的递归模板来执行此操作?
根据 Piotr Skotnicki 的建议,一个简单的解决方案是将第一个参数与以下参数分开,并使用 &&
作为折叠运算符
进行检查
例如,下面的函数 return true
if all arguments are equals
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
作业中,我收到了很多我不知道如何处理的警告("expression result unused",来自 clang++,以及 "left operand of comma operator has no effect",来自 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...
包,以下折叠表达式的值
( (args == a0) && ... )
( (args == a0) || ... )
分别是true
和false
。
所以foo()
和bar()
的return指令可以无差别的写成
return ( (args == a0) && ... && true );
或
return ( (args == a0) && ... );
在 sizeof...(args) == 0U
的情况下也是如此。
但我倾向于忘记这类细节,而更喜欢显式(使用最后的 && true
)空值。
不幸的是,之所以不起作用,是因为布尔运算符不像在其他语言中那样在 C++ 中进行链接。所以表达式:
a == (b == c)
(你的折叠表达式会扩展到什么)会将 a
与 true
或 false
进行比较,与 b
或 [=19 无关=] 实际上是。我希望 operator<=>
会添加链接,但显然那部分被删除了。
解决方法是您必须拆分比较:
(a == b) && (b == c)
当然,这并不能很好地折叠,但您可以将所有内容与第一个元素进行比较:
(a == b) && (a == c)
也就是说:
((a0 == args) && ... )
到那时,我们只需要能够拉出第一个元素。没问题,这显然是 lambda 的用途:
template <class... Args>
constexpr 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...);
}
}
这是我在 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) && ...);
}();
我有一个采用可变参数包的函数,一开始我想检查所有元素是否相等。我能否以某种方式使用新的 C++17 折叠表达式将其简洁地写成一行?我在想
template<typename... Args>
void func (Args... args)
{
ASSERT ((args == ...));
// more code here...
}
但这不起作用,因为它编译为代码,首先正确比较最后两个参数,然后将倒数第三个参数与第一次比较的结果进行比较,这是一个布尔值。这种类型的折叠表达式可能有什么用例(类似于 args < ...
)?我是否有机会避免编写专用的递归模板来执行此操作?
根据 Piotr Skotnicki 的建议,一个简单的解决方案是将第一个参数与以下参数分开,并使用 &&
作为折叠运算符
例如,下面的函数 return true
if all arguments are equals
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
作业中,我收到了很多我不知道如何处理的警告("expression result unused",来自 clang++,以及 "left operand of comma operator has no effect",来自 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...
包,以下折叠表达式的值
( (args == a0) && ... )
( (args == a0) || ... )
分别是true
和false
。
所以foo()
和bar()
的return指令可以无差别的写成
return ( (args == a0) && ... && true );
或
return ( (args == a0) && ... );
在 sizeof...(args) == 0U
的情况下也是如此。
但我倾向于忘记这类细节,而更喜欢显式(使用最后的 && true
)空值。
不幸的是,之所以不起作用,是因为布尔运算符不像在其他语言中那样在 C++ 中进行链接。所以表达式:
a == (b == c)
(你的折叠表达式会扩展到什么)会将 a
与 true
或 false
进行比较,与 b
或 [=19 无关=] 实际上是。我希望 operator<=>
会添加链接,但显然那部分被删除了。
解决方法是您必须拆分比较:
(a == b) && (b == c)
当然,这并不能很好地折叠,但您可以将所有内容与第一个元素进行比较:
(a == b) && (a == c)
也就是说:
((a0 == args) && ... )
到那时,我们只需要能够拉出第一个元素。没问题,这显然是 lambda 的用途:
template <class... Args>
constexpr 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...);
}
}
这是我在 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) && ...);
}();