web-dev-qa-db-ja.com

折りたたみ式を使用してmax(A、max(B、max(C、D)))を実装できますか?

C++ 17の折りたたみ式で遊んでみながら、結果は型のsizeofの最大値であるmax sizeofを実装しようとしました。変数とラムダを使用する醜い折り畳みバージョンがありますが、折り畳み式と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);
_

Fold式とstd::max()を使用して同等の関数を記述したいと思います。たとえば3つの要素の場合、次のように展開する必要があります

_return std::max(sizeof (A), std::max(sizeof(B), sizeof (C)));
_

それは可能ですか?

24
NoSenseEtAl

おそらくあなたが聞きたかったものではありませんが、違います。それを行うことは不可能です(純粋に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 他のすばらしい答えを参照してください。

まだ誰もこれを回答として投稿していないので、最小限の労力でこれを行う最も簡単な方法は、この問題のために用意されている std::max() のオーバーロードを使用することです: initializer_listを取るもの:

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

ここで折り返し式を使用する場合は、関数呼び出しではなく、何らかの方法で演算子を使用して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


編集:type_sizeからTを削除するという@Barryの提案(ここでは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が好きです。 :-]

18
ildjarn

C++ 17 fold式で遊ぶだけ

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

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

または、C++ 14以降、std::max()constexprであるという事実を使用します(C++ 17の場合)

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

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

元のバージョンとそれほど違いはありません。

7
max66

もちろん問題ありません。

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などを取得します。

Fold式と_std::max_を使用して同等の関数を記述したいと思います。たとえば3つの要素の場合、次のように展開する必要があります

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

別の可能な解決策(再帰に基づくnotフォールド式の)は次のとおり

_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...>()); }
_
1
max66

フォールド式ではなく、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
Richard Hodges

ちょうど楽しみのために、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^()を呼び出す代わりに(それを実装する必要はありません)。

0
max66