web-dev-qa-db-fra.com

Puis-je implémenter max (A, max (B, max (C, D))) à l'aide d'expressions de pliage?

En essayant de jouer avec les expressions de pliage C++ 17, j'ai essayé d'implémenter max sizeof où result est au maximum de la sizeof des types . J'ai une version laide de fois qui utilise une variable et un lambda, Pensez à une façon d'utiliser les expressions de pliage et std::max() pour obtenir le même résultat.

Ceci est ma version de pli:

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

Je voudrais écrire une fonction équivalente en utilisant des expressions de pliage et std::max(). Par exemple, pour 3 éléments, 

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

Est-il possible de faire ça?

24
NoSenseEtAl

Probablement pas ce que vous vouliez entendre, mais non. Il n’est pas possible de faire cela (purement1) avec des expressions de plis. Leur grammaire même ne le permet tout simplement pas:

[expr.prim.fold]

Une expression de pli effectue le pli d'un groupe de paramètres de modèle sur un opérateur binaire.

fold-expression:
  ( cast-expression fold-operator ... )
  ( ... fold-operator cast-expression )
  ( cast-expression fold-operator ... fold-operator cast-expression )
fold-operator: one of
  +   -   *   /   %   ^   &   |   <<   >> 
  +=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=  =
  ==  !=  <   >   <=  >=  &&  ||  ,    .*   ->*

Tout simplement parce qu'une expression d'appel de fonction n'est pas un opérateur binaire au sens pur de la grammaire.


1 Reportez-vous aux autres réponses superbes .

24
StoryTeller

Comme personne n’a encore répondu à cette question, le moyen le plus simple de le faire avec un minimum d’efforts consiste à utiliser la surcharge de std::max() qui est prête à l'emploi pour ce problème: celui qui prend un initializer_list:

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

Juste pour jouer avec les expressions c ++ 17 fois

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

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

ou, en utilisant le fait que std::max() est constexpr à partir de C++ 14 (c'est donc en C++ 17)

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

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

Pas vraiment différent de votre version originale.

6
max66

Bien sûr pas de problème.

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

code de test:

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

Exemple live .

Personnellement je trouve

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

ennuyeux d'écrire tout le temps, donc

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

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

le fait 

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

ou même

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

ce qui semble plus expressif, et ne donne aucun droit exact/constexpr, etc.

2

Pas une expression de pli, mais une autre façon que c ++ 17 offre - 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

Juste pour le plaisir, une variation sur le thème de la brillante solution de 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; }

Un peu simplifié car (a) la classe auxiliaire utilise uniquement la fonction sizeof() de type (résolue directement dans max_sizeof(), (b) aucune utilisation de la valeur terminale basée sur void et zéro, (c) operator^() est déclaré mais non implémenté (il n'est pas nécessaire de l'implémenter: intérêt uniquement pour le type de retour) et (d) max_sizeof() utilise decltype() au lieu d'appeler operator^() (donc il n’est pas nécessaire de le mettre en œuvre).

0
max66