c++ - ¿Puedo implementar max(A, max(B, max(C, D)) usando expresiones de plegado?
templates variadic-templates (8)
Me gustaría escribir una función equivalente usando expresiones de doblez y
std::max
. Por ejemplo para 3 elementos se debe expandir a
return std::max(sizeof (A), std::max(sizeof(B), sizeof (C)));
Otra posible solución (basada en la recursión, no en la expresión de plegado) es la siguiente
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...>()); }
Mientras intentaba jugar con las expresiones de C ++ 17 fold, intenté implementar el sizeof
máximo donde el resultado es el máximo del sizeof
de los tipos. Tengo una versión fea que utiliza variable y una lambda, pero no puedo pensar en una forma de usar expresiones de doblez y std::max()
para obtener el mismo resultado.
Esta es mi versión plegable:
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);
Me gustaría escribir una función equivalente usando expresiones de doblez y std::max()
. Por ejemplo para 3 elementos se debe expandir a
return std::max(sizeof (A), std::max(sizeof(B), sizeof (C)));
¿Es posible hacer eso?
Claro, no hay problema.
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)}; }
código de prueba:
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";
}
Personalmente me encuentro
auto maxer = [](auto&&...args){return (std::max)(decltype(args)(args)...);};
molesto escribir todo el tiempo, así que
#define RETURNS(...) /
noexcept(noexcept(__VA_ARGS__)) /
-> decltype(__VA_ARGS__) /
{ return __VA_ARGS__; }
#define OVERLOADS_OF(...) /
[](auto&&...args) /
RETURNS( __VA_ARGS__( decltype(args)(args)... ) )
lo hace
template<class...Xs>
auto result3( Xs... xs ) {
return (foldable(OVERLOADS_OF((std::max))) * ... * xs).get();
}
o incluso
template<class...Xs>
constexpr auto result4( Xs... xs )
RETURNS( (foldable(OVERLOADS_OF((std::max))) * ... * xs).get() )
lo que parece ser más expresivo, y obtiene noexcept / constexpr right, etc.
No es una expresión de plegado, sino otra forma en que c ++ 17 ofrece, 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;
}
Probablemente no es lo que querías escuchar, pero no. No es posible hacer eso (puramente 1 ) con expresiones de doblez. Su misma gramática simplemente no lo permite:
Una expresión de pliegue realiza un pliegue de un paquete de parámetros de plantilla sobre un operador binario .
fold-expression: ( cast-expression fold-operator ... ) ( ... fold-operator cast-expression ) ( cast-expression fold-operator ... fold-operator cast-expression ) fold-operator: one of + - * / % ^ & | << >> += -= *= /= %= ^= &= |= <<= >>= = == != < > <= >= && || , .* ->*
Simplemente porque una expresión de llamada de función no es un operador binario en el sentido de la gramática pura.
1 Consulte las otras respuestas excelentes.
Si quiere usar expresiones de plegado aquí, entonces necesita usar un operador para invocar std::max
lugar de una llamada de función. Aquí hay un ejemplo abusando del operator^
para ese fin:
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>{} ^ ...);
}
EDITAR: la sugerencia de @ Barry de eliminar T
de type_size
(renombrado max_val
aquí):
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)>{} ^ ...);
}
Externamente, ambas implementaciones son equivalentes; en términos de implementación, yo personalmente prefiero el primero, pero YMMV. : -]
Solo para jugar con c ++ 17 expresiones de plegado.
template <typename ... Ts>
constexpr std::size_t max_sizeof ()
{
std::size_t ret { 0 };
return ( (ret = (sizeof(Ts) > ret ? sizeof(Ts) : ret)), ... );
}
o, usando el hecho de que std::max()
es constexpr
partir de C ++ 14 (por lo que está en C ++ 17)
template <typename ... Ts>
constexpr std::size_t max_sizeof ()
{
std::size_t ret { 0 };
return ( (ret = std::max(sizeof(Ts), ret)), ... );
}
No es realmente diferente de su versión original.
Solo por diversión, una variación del tema sobre la brillante solución 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 poco simplificado porque (a) la clase auxiliar usa solo el sizeof()
del tipo (resuelto directamente en max_sizeof()
, (b) no usa el valor del terminal en función de void
y cero, (c) el operator^()
se declara pero no se implementa (no hay necesidad de implementarlo: interés solo por el tipo de devolución) y (d) max_sizeof()
usa decltype()
lugar de llamar al operator^()
(por lo que no es necesario implementarlo).
Ya que nadie publicó este como una respuesta todavía, la forma más fácil de hacerlo con un esfuerzo mínimo es simplemente usar la sobrecarga de std::max()
que está preparada para este problema: la que toma una lista de initializer_list
:
template<typename... T>
constexpr size_t max_sizeof() {
return std::max({sizeof(T)...});
}