【问题标题】:Can I implement max(A, max(B, max(C, D))) using fold expressions?我可以使用折叠表达式实现 max(A, max(B, max(C, D))) 吗?
【发布时间】:2018-03-06 09:09:33
【问题描述】:

在尝试使用 C++17 折叠表达式时,我尝试实现 max sizeof,其中结果是 sizeof 类型的最大值。 我有一个使用变量和 lambda 的丑陋折叠版本,但我想不出一种方法来使用折叠表达式和 std::max() 来获得相同的结果。

这是我的折叠版本:

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)));

有可能吗?

【问题讨论】:

  • max(std::initializer_list&lt;T&gt;) 存在。
  • 是否有理由使用折叠,而不仅仅是template&lt;typename... T&gt; constexpr size_t max_sizeof(){ return std::max({sizeof(T)...}); }
  • @DaveS 为什么它不起作用? live on coliru
  • @schorsch312 : 那么它不可能是constexpr
  • template&lt;class... Ts&gt; constexpr std::size_t max_sizeof = sizeof(std::aligned_union_t&lt;1, Ts...&gt;);

标签: c++ templates variadic-templates c++17 fold-expression


【解决方案1】:

可能不是您想听到的,但不是。使用折叠表达式是不可能做到这一点的(纯粹的1)。他们的语法根本不允许这样做:

[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 参考其他优秀的答案。

【讨论】:

  • 那么如果 OP 可以定义一个二元运算符(例如,operator,)处理包装的size_t,他们可以使用折叠表达式吗?
  • @YSC - 我不敢这么说,但是可以。虽然我不认为它比 OP 想要避免的版本更“丑陋”。
【解决方案2】:

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

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

【讨论】:

  • 这应该是公认的答案
【解决方案3】:

如果你想在这里使用折叠表达式,那么你需要以某种方式使用运算符来调用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>{} ^ ...);
}

Online Demo


编辑:@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)>{} ^ ...);
}

Online Demo

在外部,两种实现是等价的;在实现方面,我个人更喜欢前者,但是YMMV。 :-]

【讨论】:

  • @NoSenseEtAl : C++/CLI 有 ^ 来指定句柄,这些句柄是托管等价于指针,但它不是一个运算符(C++/CLI 确实添加了一个运算符,operator%,相当于原生一元operator*)。 ^ 是二元运算符,默认按位异或。
  • 可以只使用template &lt;size_t N&gt; struct type_size : integral_constant&lt;size_t, N&gt; { },而不必使用void
  • @Barry :当然,我只是想避免在max_sizeof 本身内部使用sizeof。您的方法总体上会更简洁,但我正在尝试对(主观)可读性进行一些小的尝试。 :-P
  • 如果您喜欢,请随时从我的回答中吸收元素;顺便说一句:你确定^ type_size&lt;void, 0&gt;{} 部分是必要的吗?
【解决方案4】:

只是为了玩 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()constexpr 从 C++14 开始的事实(所以它在 C++17 中)

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

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

与您的原始版本没有真正不同。

【讨论】:

  • 这个不好读……有没有办法把这个改写成折叠递归的形式?
  • 我赞成你在技术上是正确的,但我希望有人能想出更好的解决方案,因为像 YSC 我在解析这个时遇到了麻烦。我的意思是,自从我问了这个问题以来,我现在可以猜到它现在做了什么,但是如果这段代码来找我审查,我会感到困惑。 :)
  • @YSC - 好吧......不是很出色,但是(恕我直言)对于熟练的 C++ 程序员来说应该很容易阅读;无论如何,我添加了一个使用 std::max() 的版本,应该更容易;但是...“折叠递归形式”是什么意思
  • @NoSenseEtAl - 不幸的是,新的 c++17 折叠表达式适用于运算符;我能想象的最好的方法是使用逗号作为运算符,在每个sizeof() 上迭代一些东西; “某事”可以是基于三元运算符或std::max() 或其他的赋值,但我认为不可能做得更好。
  • @NoSenseEtAl - 无论如何,看看 ildjarn 的精彩回答:他重新定义了一个运算符来获得你想要的东西。
【解决方案5】:

当然,没问题。

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";
}

Live example.

我个人觉得

  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 权利等。

【讨论】:

    【解决方案6】:

    我想使用折叠表达式和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...>()); }
    

    【讨论】:

    • 我考虑过,但现在我更愿意在可能的情况下编写没有特殊情况的函数,IIRC 这是折叠表达式的卖点之一。
    • @NoSenseEtAl - 我明白了......但是没有折叠表达式的特殊情况(在你的特定问题中)很明显:对应于 ret 的初始化为零,在我的另一个答案,或 ildjarn 解决方案中 type_size&lt;void, 0&gt;{} 类型的对象。无论如何,我认为基于折叠表达式的解决方案更可取,因为模板递归存在限制。
    【解决方案7】:

    不是折叠表达式,而是 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;
    }
    

    【讨论】:

    • @NoSenseEtAl 我认为最多一项等同于身份,但会更新以提供这一点。
    【解决方案8】:

    只是为了好玩,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&lt;sizeof(T)&gt;{} ^ ...);
    • @Barry - 是的;但我的想法是在正文中使用decltype()(我重复一遍:只是为了好玩)。
    【解决方案9】:

    这个怎么样(由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

    【讨论】:

      猜你喜欢
      • 2017-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-31
      • 1970-01-01
      • 1970-01-01
      • 2018-06-19
      • 1970-01-01
      相关资源
      最近更新 更多