我可以使用折叠表达式实现max(A, max(B, max(C, D)))吗?

29
尝试使用C++17折叠表达式时,我试图实现max sizeof,其中结果是类型sizeof的最大值。我有一个丑陋的fold版本,使用变量和lambda表达式,但我无法想出如何使用fold表达式和std::max()来获得相同的结果。
这是我的fold版本:
template<typename... T>
constexpr size_t max_sizeof(){
    size_t max=0;
    auto update_max = [&max](const size_t& size) {if (max<size) max=size; };
    (update_max(sizeof (T)), ...);
    return max;
}


static_assert(max_sizeof<int, char, double, short>() == 8);
static_assert(max_sizeof<char, float>() == sizeof(float));
static_assert(max_sizeof<int, char>() == 4);

我想使用折叠表达式和std::max()编写等效函数。例如,对于3个元素,它应该扩展为

return std::max(sizeof (A), std::max(sizeof(B), sizeof (C)));

这是可行的吗?


23
max(std::initializer_list<T>) 存在。 - Jarod42
6
是否有使用折叠表达式的理由,而不只是template<typename... T> constexpr size_t max_sizeof(){ return std::max({sizeof(T)...}); }呢? - Dave S
1
@YSC 你忘记了#include <algorithm> - nwp
7
那么它就无法成为constexpr - ildjarn
2
template<class... Ts> constexpr std::size_t max_sizeof = sizeof(std::aligned_union_t<1, Ts...>); - Casey
显示剩余2条评论
9个回答

28

由于还没有人把这个作为答案发布,最简单的方法是只使用已经为此问题准备好的重载std::max():那就是使用一个initializer_list

template<typename... T>
constexpr size_t max_sizeof() {
    return std::max({sizeof(T)...});
}

这应该是被接受的答案。 - Rishabh Deep Singh

26

可能不是你想听到的,但不行。使用折叠表达式(purely1)做那个(事情)是不可能的。它们的语法根本就不允许这样做:

[expr.prim.fold]

一个折叠表达式对模板参数包执行了一个二元操作符的折叠。

fold-expression:
  ( cast-expression fold-operator ... )
  ( ... fold-operator cast-expression )
  ( cast-expression fold-operator ... fold-operator cast-expression )
fold-operator: one of
  +   -   *   /   %   ^   &   |   <<   >> 
  +=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=  =
  ==  !=  <   >   <=  >=  &&  ||  ,    .*   ->*
仅仅因为在语法上的定义中,函数调用表达式不是二元运算符。

1 请参考其他卓越的答案。


2
那么,如果 OP 可以定义一个作用于包装的 size_t 的二元运算符(比如说 operator,),他们就可以使用折叠表达式了吗? - YSC
1
@YSC - 我有些犹豫,但是是的。虽然我认为它并不比OP想要避免的版本更“丑陋”。 - StoryTeller - Unslander Monica

19
如果您想在这里使用折叠表达式,则需要使用运算符来调用std::max而不是函数调用。以下是一个滥用operator^的示例:
namespace detail {
    template<typename T, std::size_t N = sizeof(T)>
    struct type_size : std::integral_constant<std::size_t, N> { };

    template<typename T, auto M, typename U, auto N>
    constexpr auto operator ^(type_size<T, M>, type_size<U, N>) noexcept {
        return type_size<void, std::max(M, N)>{};
    }
}

template<typename... T>
constexpr std::size_t max_sizeof() noexcept {
    using detail::type_size;
    return (type_size<T>{} ^ ... ^ type_size<void, 0>{});
    // or, if you don't care to support empty packs
    // return (type_size<T>{} ^ ...);
}

在线演示


编辑:@Barry的建议是从type_size中删除T(此处重命名为max_val):

namespace detail {
    template<auto N>
    struct max_val : std::integral_constant<decltype(N), N> { };

    template<auto M, auto N, auto R = std::max(M, N)>
    constexpr max_val<R> operator ^(max_val<M>, max_val<N>) noexcept {
        return {};
    }
}

template<typename... T>
constexpr std::size_t max_sizeof() noexcept {
    using detail::max_val;
    return (max_val<sizeof(T)>{} ^ ... ^ max_val<std::size_t{}>{});
    // or, if you don't care to support empty packs
    // return (max_val<sizeof(T)>{} ^ ...);
}

在线演示

从外部来看,这两个实现是相等的;在实现方面,我个人更喜欢前者,但你的想法可能有所不同。:-]


3
C++/CLI中有一个符号^,用于表示句柄(handle),它是指针(pointer)的托管等效物。但是它不是一个运算符(C++/CLI确实添加了一个运算符operator%,相当于本地的一元operator*)。默认情况下,这个^是一个位XOR二进制运算符。 - ildjarn
2
可以直接使用 template <size_t N> struct type_size : integral_constant<size_t, N> { },避免使用 void - Barry
@Barry:当然,我只是试图避免在max_sizeof内部使用sizeof。你的方法总体上更简洁,但我试图尝试一些(主观上的)可读性。:-P - ildjarn
1
无论如何,如果您喜欢的话,请随意吸收我的回答中的元素;顺便提一句:您确定^ type_size<void, 0>{}部分是必需的吗? - max66

10

只是为了尝试使用 C++17 的折叠表达式

template <typename ... Ts>
constexpr std::size_t max_sizeof ()
 {
   std::size_t  ret { 0 };

   return ( (ret = (sizeof(Ts) > ret ? sizeof(Ts) : ret)), ... ); 
 }

或者,利用 std::max() 从 C++14 开始成为 constexpr (因此它也适用于C++17)的事实。

template <typename ... Ts>
constexpr std::size_t max_sizeof ()
 {
   std::size_t  ret { 0 };

   return ( (ret = std::max(sizeof(Ts), ret)), ... ); 
 }

与您的原始版本并没有真正的区别。


1
这不容易阅读... 没有办法将其重写为折叠递归形式吗? - YSC
我给了你赞,因为技术上是正确的,但我希望有人能想出更好的解决方案,因为像YSC一样,我也很难解析这个代码。我的意思是,现在我可以猜测它的作用,因为我问了这个问题,但如果这段代码交给我审核,我会感到困惑。:) - NoSenseEtAl
@YSC - 嗯... 不算特别出色,但(在我看来)对于一个有经验的 C++ 程序员来说应该很容易读懂;无论如何,我已经添加了一个使用 std::max() 的版本,应该更容易;但是... 你所说的“折叠递归形式”是什么意思? - max66
3
很抱歉,新的C++17折叠表达式只能使用运算符,最好的解决办法是使用逗号作为运算符,在每个sizeof()上迭代一些“东西”;这个“东西”可以是基于三目运算符或std::max()等的赋值,但我认为不可能做得更好。 - max66
3
无论如何,看看ildjarn的精彩答案:他重新定义了一个运算符以获得你想要的结果。 - max66

3
当然,没问题。
template<class Lhs, class F>
struct foldable_binop_t {
  Lhs lhs;
  F f;
  template<class Rhs>
  auto operator*(Rhs&& rhs) &&
  -> foldable_binop_t< std::result_of_t<F&(Lhs&&, Rhs&&)>, F >
  {
    return { f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)), std::forward<F>(f) };
  }
  Lhs operator()() && { return std::forward<Lhs>(lhs); }
  operator Lhs() && { return std::move(*this)(); }
  Lhs get() && { return std::move(*this); }
};
template<class F>
struct foldable_t {
  F f;
  template<class Lhs>
  friend foldable_binop_t<Lhs, F> operator*( Lhs&& lhs, foldable_t&& self ) {
    return {std::forward<Lhs>(lhs), std::forward<F>(self.f)};
  }
  template<class Rhs>
  foldable_binop_t<Rhs, F> operator*( Rhs&& rhs ) && {
    return {std::forward<Rhs>(rhs), std::forward<F>(f)};
  }
};
template<class F>
foldable_t<F> foldable(F f) { return {std::move(f)}; }

测试代码:

template<class...Xs>
auto result( Xs... xs ) {
  auto maxer = [](auto&&...args){return (std::max)(decltype(args)(args)...);};
  return ((0 * foldable(maxer)) * ... * xs).get();
}
template<class...Xs>
auto result2( Xs... xs ) {
  auto maxer = [](auto&&...args){return (std::max)(decltype(args)(args)...);};
  return (foldable(maxer) * ... * xs).get();
}

int main() {
  int x = result2( 0, 7, 10, 11, -3 ); // or result
  std::cout << x << "\n";
}

实时示例

个人认为

  auto maxer = [](auto&&...args){return (std::max)(decltype(args)(args)...);};

每次写这个很烦人,因此

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__( decltype(args)(args)... ) )

使它更加...
template<class...Xs>
auto result3( Xs... xs ) {
  return (foldable(OVERLOADS_OF((std::max))) * ... * xs).get();
}

或者甚至

template<class...Xs>
constexpr auto result4( Xs... xs )
  RETURNS( (foldable(OVERLOADS_OF((std::max))) * ... * xs).get() )

它似乎更具表现力,并且正确使用了noexcept / constexpr等功能。


1
我希望使用折叠表达式和std::max编写等效函数。例如,对于3个元素,它应该扩展为return std::max(sizeof(A), std::max(sizeof(B), sizeof(C)));
另一个可能的解决方案(基于递归,但不是折叠表达式)如下:
template <typename T0>
constexpr std::size_t max_sizeof ()
 { return sizeof(T0); }
    
template <typename T0, typename T1, typename ... Ts>
constexpr std::size_t max_sizeof ()
 { return std::max(sizeof(T0), max_sizeof<T1, Ts...>()); }

我曾经考虑过这个问题,但现在我更喜欢尽可能地避免写特别情况的函数。如果我没记错的话,这也是折叠表达式的卖点之一。 - NoSenseEtAl
1
@NoSenseEtAl - 我明白了...但是在你的特定问题中缺少折叠表达式的特殊情况只是表面现象:对于ret的初始化为零,这在我的另一个答案中,或者在ildjarn解决方案中的类型为type_size<void, 0>{}的对象相对应。无论如何,我认为基于折叠表达式的解决方案更可取,因为模板递归存在限制。 - max66

1

不是折叠表达式,而是C++17提供的另一种方式 - if constexpr

template<class X, class Y, class...Ts>
constexpr std::size_t max_sizeof()
{
    auto base = std::max(sizeof(X), sizeof(Y));

    if constexpr (sizeof...(Ts) == 0)
    {
        // nothing
    }
    else if constexpr (sizeof...(Ts) == 1)
    {
        base = std::max(base, sizeof(Ts)...);
    }
    else
    {
        base = std::max(base, max_sizeof<Ts...>());
    }
    return base;
}

1
@NoSenseEtAl 我认为一个元素的最大值等同于恒等元素,但我会进行更新以提供这个信息。 - Richard Hodges

1

仅供娱乐,这是从 ildjarn 的杰出解决方案主题的变体

namespace detail
 {
   template <std::size_t N>
   struct tSizeH : std::integral_constant<std::size_t, N> { };

   template <std::size_t M, std::size_t N>
   constexpr tSizeH<std::max(M, N)> operator^ (tSizeH<M>, tSizeH<N>);
 }

template <typename ... T>
constexpr std::size_t max_sizeof() noexcept
 { return decltype((detail::tSizeH<sizeof(T)>{} ^ ...))::value; }

这里稍微简化了一下,因为 (a) 帮助类仅使用类型的 sizeof()(在 max_sizeof() 中直接解决),(b) 没有使用基于 void 和零的终端值,(c) 声明了 operator^() 但未实现(只关心返回类型)和 (d) max_sizeof() 使用 decltype() 而不是调用 operator^()(所以无需实现它)。


如果你已经实现了它(只需 return {}),那么你的代码可以简化为 return (detail::tSizeH<sizeof(T)>{} ^ ...); - Barry
1
@Barry - 是的;但我的想法是在函数体中使用decltype()(我再重申一遍:只是为了好玩)。 - max66

0
这个怎么样(由https://articles.emptycrate.com/2016/05/14/folds_in_cpp11_ish.html提供):
template<typename U, typename ... V>
constexpr auto max(const U &u, const V &... v) -> typename std::common_type<U, V...>::type {
  using rettype = typename std::common_type<U, V...>::type;
  rettype result = static_cast<rettype>(u);
  (void)std::initializer_list<int>{ ( (v > result)?(result = static_cast<rettype>(v), 0):0 )... };
  return result;
}

它在c++14中工作,因此不使用c++17的折叠表达式,但它可以像这里https://godbolt.org/z/6oWvK9所示那样工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接