/***************************************************************************
 * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and         *
 * Martin Renou                                                             *
 * Copyright (c) QuantStack                                                 *
 * Copyright (c) Serge Guelton                                              *
 *                                                                          *
 * Distributed under the terms of the BSD 3-Clause License.                 *
 *                                                                          *
 * The full license is in the file LICENSE, distributed with this software. *
 ****************************************************************************/

#ifndef XSIMD_API_HPP
#define XSIMD_API_HPP

#include <complex>
#include <cstddef>
#include <limits>
#include <ostream>

#include "../arch/xsimd_isa.hpp"
#include "../types/xsimd_batch.hpp"
#include "../types/xsimd_traits.hpp"

namespace xsimd
{

    /**
     * high level free functions
     *
     * @defgroup batch_arithmetic Arithmetic operators
     * @defgroup batch_constant Constant batches
     * @defgroup batch_data_transfer Memory operators
     * @defgroup batch_math Basic math operators
     * @defgroup batch_math_extra Extra math operators
     * @defgroup batch_fp Floating point manipulation
     * @defgroup batch_rounding Rounding operators
     * @defgroup batch_conversion Conversion operators
     * @defgroup batch_complex_op Complex operators
     * @defgroup batch_logical Logical operators
     * @defgroup batch_bitwise Bitwise operators
     * @defgroup batch_reducers Reducers
     * @defgroup batch_miscellaneous Miscellaneous
     * @defgroup batch_trigo Trigonometry
     *
     * @defgroup batch_bool_logical Boolean logical operators
     * @defgroup batch_bool_reducers Boolean reducers
     */

    /**
     * @ingroup batch_math
     *
     * Computes the absolute values of each scalar in the batch \c x.
     * @param x batch of integer or floating point values.
     * @return the absolute values of \c x.
     */
    template <class T, class A>
    inline batch<T, A> abs(batch<T, A> const& x) noexcept
    {
        return kernel::abs<A>(x, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the absolute values of each complex in the batch \c z.
     * @param z batch of complex values.
     * @return the absolute values of \c z.
     */
    template <class T, class A>
    inline batch<T, A> abs(batch<std::complex<T>, A> const& z) noexcept
    {
        return kernel::abs<A>(z, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the sum of the batches \c x and \c y.
     * @param x batch or scalar involved in the addition.
     * @param y batch or scalar involved in the addition.
     * @return the sum of \c x and \c y
     */
    template <class T, class Tp>
    inline auto add(T const& x, Tp const& y) noexcept -> decltype(x + y)
    {
        return x + y;
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the arc cosine of the batch \c x.
     * @param x batch of floating point values.
     * @return the arc cosine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> acos(batch<T, A> const& x) noexcept
    {
        return kernel::acos<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the inverse hyperbolic cosine of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse hyperbolic cosine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> acosh(batch<T, A> const& x) noexcept
    {
        return kernel::acosh<A>(x, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the argument of the batch \c z.
     * @param z batch of complex or real values.
     * @return the argument of \c z.
     */
    template <class T, class A>
    inline real_batch_type_t<batch<T, A>> arg(batch<T, A> const& z) noexcept
    {
        return kernel::arg<A>(z, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the arc sine of the batch \c x.
     * @param x batch of floating point values.
     * @return the arc sine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> asin(batch<T, A> const& x) noexcept
    {
        return kernel::asin<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the inverse hyperbolic sine of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse hyperbolic sine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> asinh(batch<T, A> const& x) noexcept
    {
        return kernel::asinh<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the arc tangent of the batch \c x.
     * @param x batch of floating point values.
     * @return the arc tangent of \c x.
     */
    template <class T, class A>
    inline batch<T, A> atan(batch<T, A> const& x) noexcept
    {
        return kernel::atan<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the arc tangent of the batch \c x/y, using the signs of the
     * arguments to determine the correct quadrant.
     * @param x batch of floating point values.
     * @param y batch of floating point values.
     * @return the arc tangent of \c x/y.
     */
    template <class T, class A>
    inline batch<T, A> atan2(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::atan2<A>(x, y, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the inverse hyperbolic tangent of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse hyperbolic tangent of \c x.
     */
    template <class T, class A>
    inline batch<T, A> atanh(batch<T, A> const& x) noexcept
    {
        return kernel::atanh<A>(x, A {});
    }

    /**
     * @ingroup batch_conversion
     *
     * Perform a static_cast from \c T_in to \c T_out on \c \c x.
     * @param x batch of \c T_in
     * @return \c x casted to \c T_out
     */
    template <class T_out, class T_in, class A>
    inline batch<T_out, A> batch_cast(batch<T_in, A> const& x) noexcept
    {
        return kernel::batch_cast<A>(x, batch<T_out, A> {}, A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Computes the bit of sign of \c x
     * @param x batch of scalar
     * @return bit of sign of \c x
     */
    template <class T, class A>
    inline batch<T, A> bitofsign(batch<T, A> const& x) noexcept
    {
        return kernel::bitofsign<A>(x, A {});
    }

    /**
     * @ingroup batch_bitwise
     *
     * Computes the bitwise and of the batches \c x and \c y.
     * @param x batch involved in the operation.
     * @param y batch involved in the operation.
     * @return the result of the bitwise and.
     */
    template <class T, class Tp>
    inline auto bitwise_and(T const& x, Tp const& y) noexcept -> decltype(x & y)
    {
        return x & y;
    }

    /**
     * @ingroup batch_bitwise
     *
     * Computes the bitwise and not of batches \c x and \c y.
     * @param x batch involved in the operation.
     * @param y batch involved in the operation.
     * @return the result of the bitwise and not.
     */
    template <class T, class A>
    inline batch<T, A> bitwise_andnot(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::bitwise_andnot<A>(x, y, A {});
    }

    /**
     * @ingroup batch_bool_logical
     *
     * Computes the bitwise and not of batches \c x and \c y.
     * @param x batch involved in the operation.
     * @param y batch involved in the operation.
     * @return the result of the bitwise and not.
     */
    template <class T, class A>
    inline batch_bool<T, A> bitwise_andnot(batch_bool<T, A> const& x, batch_bool<T, A> const& y) noexcept
    {
        return kernel::bitwise_andnot<A>(x, y, A {});
    }

    /**
     * @ingroup batch_conversion
     *
     * Perform a reinterpret_cast from \c T_in to \c T_out on \c x.
     * @param x batch of \c T_in
     * @return \c x reinterpreted as \c T_out
     */
    template <class B, class T, class A>
    inline B bitwise_cast(batch<T, A> const& x) noexcept
    {
        return kernel::bitwise_cast<A>(x, B {}, A {});
    }

    /**
     * @ingroup batch_bitwise
     *
     * Computes the bitwise not of batch \c x.
     * @param x batch involved in the operation.
     * @return the result of the bitwise not.
     */
    template <class T, class A>
    inline batch<T, A> bitwise_not(batch<T, A> const& x) noexcept
    {
        return kernel::bitwise_not<A>(x, A {});
    }

    /**
     * @ingroup batch_bitwise
     *
     * Computes the bitwise or of the batches \c x and \c y.
     * @param x scalar or batch of scalars
     * @param y scalar or batch of scalars
     * @return the result of the bitwise or.
     */
    template <class T, class Tp>
    inline auto bitwise_or(T const& x, Tp const& y) noexcept -> decltype(x | y)
    {
        return x | y;
    }

    /**
     * @ingroup batch_bitwise
     *
     * Computes the bitwise xor of the batches \c x and \c y.
     * @param x scalar or batch of scalars
     * @param y scalar or batch of scalars
     * @return the result of the bitwise xor.
     */
    template <class T, class Tp>
    inline auto bitwise_xor(T const& x, Tp const& y) noexcept -> decltype(x ^ y)
    {
        return x ^ y;
    }

    // FIXME: check if these need to be exposed, or removed (?)
    template <class A>
    inline batch_bool<float, A> bool_cast(batch_bool<int32_t, A> const& x) noexcept
    {
        return kernel::bool_cast<A>(x, A {});
    }
    template <class A>
    inline batch_bool<int32_t, A> bool_cast(batch_bool<float, A> const& x) noexcept
    {
        return kernel::bool_cast<A>(x, A {});
    }
    template <class A>
    inline batch_bool<double, A> bool_cast(batch_bool<int64_t, A> const& x) noexcept
    {
        return kernel::bool_cast<A>(x, A {});
    }
    template <class A>
    inline batch_bool<int64_t, A> bool_cast(batch_bool<double, A> const& x) noexcept
    {
        return kernel::bool_cast<A>(x, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the single value \c v.
     * @param v the value used to initialize the batch
     * @return a new batch instance
     */
    template <class T, class A = default_arch>
    inline batch<T, A> broadcast(T v) noexcept
    {
        return kernel::broadcast<A>(v, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the single value \c v and
     * the specified batch value type \c To.
     * @param v the value used to initialize the batch
     * @return a new batch instance
     */
    template <class To, class A = default_arch, class From>
    inline simd_return_type<From, To> broadcast_as(From v) noexcept
    {
        using batch_value_type = typename simd_return_type<From, To>::value_type;
        using value_type = typename std::conditional<std::is_same<From, bool>::value,
                                                     bool,
                                                     batch_value_type>::type;
        return simd_return_type<From, To>(value_type(v));
    }

    /**
     * @ingroup batch_math
     *
     * Computes the cubic root of the batch \c x.
     * @param x batch of floating point values.
     * @return the cubic root of \c x.
     */
    template <class T, class A>
    inline batch<T, A> cbrt(batch<T, A> const& x) noexcept
    {
        return kernel::cbrt<A>(x, A {});
    }

    /**
     * @ingroup batch_rounding
     *
     * Computes the batch of smallest integer values not less than
     * scalars in \c x.
     * @param x batch of floating point values.
     * @return the batch of smallest integer values not less than \c x.
     */
    template <class T, class A>
    inline batch<T, A> ceil(batch<T, A> const& x) noexcept
    {
        return kernel::ceil<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Clips the values of the batch \c x between those of the batches \c lo and \c hi.
     * @param x batch of floating point values.
     * @param lo batch of floating point values.
     * @param hi batch of floating point values.
     * @return the result of the clipping.
     */
    template <class A, class T>
    inline batch<T, A> clip(batch<T, A> const& x, batch<T, A> const& lo, batch<T, A> const& hi) noexcept
    {
        return kernel::clip(x, lo, hi, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the conjugate of the batch \c z.
     * @param z batch of complex values.
     * @return the argument of \c z.
     */
    template <class A, class T>
    inline complex_batch_type_t<batch<T, A>> conj(batch<T, A> const& z) noexcept
    {
        return kernel::conj(z, A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Computes a value whose  absolute  value  matches
     *        that of \c x, but whose sign bit matches that of \c y.
     * @param x batch of scalars
     * @param y batch of scalars
     * @return batch whose absolute  value  matches that of \c x, but whose sign bit
     * matches that of \c y.
     */
    template <class A, class T>
    inline batch<T, A> copysign(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::copysign<A>(x, y, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the cosine of the batch \c x.
     * @param x batch of floating point values.
     * @return the cosine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> cos(batch<T, A> const& x) noexcept
    {
        return kernel::cos<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * computes the hyperbolic cosine of the batch \c x.
     * @param x batch of floating point values.
     * @return the hyperbolic cosine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> cosh(batch<T, A> const& x) noexcept
    {
        return kernel::cosh<A>(x, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the division of the batch \c x by the batch \c y.
     * @param x scalar or batch of scalars
     * @param y scalar or batch of scalars
     * @return the result of the division.
     */
    template <class T, class Tp>
    inline auto div(T const& x, Tp const& y) noexcept -> decltype(x / y)
    {
        return x / y;
    }

    /**
     * @ingroup batch_logical
     *
     * Element-wise equality comparison of batches \c x and \c y.
     * @param x batch of scalars
     * @param y batch of scalars
     * @return a boolean batch.
     */
    template <class T, class A>
    inline batch_bool<T, A> eq(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return x == y;
    }

    /**
     * @ingroup batch_math
     *
     * Computes the natural exponential of the batch \c x.
     * @param x batch of floating point values.
     * @return the natural exponential of \c x.
     */
    template <class T, class A>
    inline batch<T, A> exp(batch<T, A> const& x) noexcept
    {
        return kernel::exp<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the base 10 exponential of the batch \c x.
     * @param x batch of floating point values.
     * @return the base 10 exponential of \c x.
     */
    template <class T, class A>
    inline batch<T, A> exp10(batch<T, A> const& x) noexcept
    {
        return kernel::exp10<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the base 2 exponential of the batch \c x.
     * @param x batch of floating point values.
     * @return the base 2 exponential of \c x.
     */
    template <class T, class A>
    inline batch<T, A> exp2(batch<T, A> const& x) noexcept
    {
        return kernel::exp2<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the natural exponential of the batch \c x, minus one.
     * @param x batch of floating point values.
     * @return the natural exponential of \c x, minus one.
     */
    template <class T, class A>
    inline batch<T, A> expm1(batch<T, A> const& x) noexcept
    {
        return kernel::expm1<A>(x, A {});
    }

    /**
     * @ingroup batch_math_extra
     *
     * Computes the error function of the batch \c x.
     * @param x batch of floating point values.
     * @return the error function of \c x.
     */
    template <class T, class A>
    inline batch<T, A> erf(batch<T, A> const& x) noexcept
    {
        return kernel::erf<A>(x, A {});
    }

    /**
     * @ingroup batch_math_extra
     *
     * Computes the complementary error function of the batch \c x.
     * @param x batch of floating point values.
     * @return the error function of \c x.
     */
    template <class T, class A>
    inline batch<T, A> erfc(batch<T, A> const& x) noexcept
    {
        return kernel::erfc<A>(x, A {});
    }

    /**
     * @ingroup batch_math_extra
     *
     * Evaluate polynomial with coefficient \c Coefs on point \c x using estrin
     * method.
     * @param x batch of floating point values.
     * @return the evaluation ofpolynomial with coefficient \c Coefs on point \c x.
     */
    template <class T, class A, uint64_t... Coefs>
    inline batch<T, A> estrin(const batch<T, A>& x) noexcept
    {
        return kernel::estrin<T, A, Coefs...>(x);
    }

    /**
     * Extract vector from pair of vectors
     * extracts the lowest vector elements from the second source \c x
     * and the highest vector elements from the first source \c y
     * Concatenates the results into th Return value.
     * @param x batch of integer or floating point values.
     * @param y batch of integer or floating point values.
     * @param i integer specifuing the lowest vector element to extract from the first source register
     * @return.
     */
    template <class T, class A>
    inline batch<T, A> extract_pair(batch<T, A> const& x, batch<T, A> const& y, std::size_t i) noexcept
    {
        return kernel::extract_pair<A>(x, y, i, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the absolute values of each scalar in the batch \c x.
     * @param x batch floating point values.
     * @return the asbolute values of \c x.
     */
    template <class T, class A>
    inline batch<T, A> fabs(batch<T, A> const& x) noexcept
    {
        return kernel::abs<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the positive difference between \c x and \c y, that is,
     * <tt>max(0, x-y)</tt>.
     * @param x batch of floating point values.
     * @param y batch of floating point values.
     * @return the positive difference.
     */
    template <class T, class A>
    inline batch<T, A> fdim(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::fdim<A>(x, y, A {});
    }

    /**
     * @ingroup batch_rounding
     *
     * Computes the batch of largest integer values not greater than
     * scalars in \c x.
     * @param x batch of floating point values.
     * @return the batch of largest integer values not greater than \c x.
     */
    template <class T, class A>
    inline batch<T, A> floor(batch<T, A> const& x) noexcept
    {
        return kernel::floor<A>(x, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes <tt>(x*y) + z</tt> in a single instruction when possible.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @param z a batch of integer or floating point values.
     * @return the result of the fused multiply-add operation.
     */
    template <class T, class A>
    inline batch<T, A> fma(batch<T, A> const& x, batch<T, A> const& y, batch<T, A> const& z) noexcept
    {
        return kernel::fma<A>(x, y, z, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the larger values of the batches \c x and \c y.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @return a batch of the larger values.
     */
    template <class T, class A>
    inline batch<T, A> fmax(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::max<A>(x, y, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the smaller values of the batches \c x and \c y.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @return a batch of the larger values.
     */
    template <class T, class A>
    inline batch<T, A> fmin(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::min<A>(x, y, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the modulo of the batch \c x by the batch \c y.
     * @param x batch involved in the modulo.
     * @param y batch involved in the modulo.
     * @return the result of the modulo.
     */
    template <class T, class A>
    inline batch<T, A> fmod(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::fmod<A>(x, y, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes <tt>(x*y) - z</tt> in a single instruction when possible.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @param z a batch of integer or floating point values.
     * @return the result of the fused multiply-sub operation.
     */
    template <class T, class A>
    inline batch<T, A> fms(batch<T, A> const& x, batch<T, A> const& y, batch<T, A> const& z) noexcept
    {
        return kernel::fms<A>(x, y, z, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes <tt>-(x*y) + z</tt> in a single instruction when possible.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @param z a batch of integer or floating point values.
     * @return the result of the fused negated multiply-add operation.
     */
    template <class T, class A>
    inline batch<T, A> fnma(batch<T, A> const& x, batch<T, A> const& y, batch<T, A> const& z) noexcept
    {
        return kernel::fnma<A>(x, y, z, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes <tt>-(x*y) - z</tt> in a single instruction when possible.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @param z a batch of integer or floating point values.
     * @return the result of the fused negated multiply-sub operation.
     */
    template <class T, class A>
    inline batch<T, A> fnms(batch<T, A> const& x, batch<T, A> const& y, batch<T, A> const& z) noexcept
    {
        return kernel::fnms<A>(x, y, z, A {});
    }

    /**
     * @ingroup batch_fp
     *
     * Split split the number x into a normalized fraction and an exponent which is stored in exp
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @return the normalized fraction of x
     */
    template <class T, class A>
    inline batch<T, A> frexp(const batch<T, A>& x, batch<as_integer_t<T>, A>& y) noexcept
    {
        return kernel::frexp<A>(x, y, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Element-wise greater or equal comparison of batches \c x and \c y.
     * @tparam X the actual type of batch.
     * @param x batch involved in the comparison.
     * @param y batch involved in the comparison.
     * @return a boolean batch.
     */
    template <class T, class A>
    inline batch_bool<T, A> ge(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return x >= y;
    }

    /**
     * @ingroup batch_logical
     *
     * Element-wise greater than comparison of batches \c x and \c y.
     * @tparam X the actual type of batch.
     * @param x batch involved in the comparison.
     * @param y batch involved in the comparison.
     * @return a boolean batch.
     */
    template <class T, class A>
    inline batch_bool<T, A> gt(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return x > y;
    }

    /**
     * @ingroup batch_reducers
     *
     * Adds all the scalars of the batch \c x.
     * @param x batch involved in the reduction
     * @return the result of the reduction.
     */
    template <class T, class A>
    inline T hadd(batch<T, A> const& x) noexcept
    {
        return kernel::hadd<A>(x, A {});
    }

    /**
     * @ingroup batch_reducers
     *
     * Parallel horizontal addition: adds the scalars of each batch
     * in the array pointed by \c row and store them in a returned
     * batch.
     * @param row an array of \c N batches
     * @return the result of the reduction.
     */
    template <class T, class A>
    inline batch<T, A> haddp(batch<T, A> const* row) noexcept
    {
        return kernel::haddp<A>(row, A {});
    }

    /**
     * @ingroup batch_math_extra
     *
     * Evaluate polynomial with coefficient \c Coefs on point \c x using horner
     * method.
     * @param x batch of floating point values.
     * @return the evaluation ofpolynomial with coefficient \c Coefs on point \c x.
     */
    template <class T, class A, uint64_t... Coefs>
    inline batch<T, A> horner(const batch<T, A>& x) noexcept
    {
        return kernel::horner<T, A, Coefs...>(x);
    }

    /**
     * @ingroup batch_math
     *
     * Computes the square root of the sum of the squares of the batches
     * \c x, and \c y.
     * @param x batch of floating point values.
     * @param y batch of floating point values.
     * @return the square root of the sum of the squares of \c x and \c y.
     */
    template <class T, class A>
    inline batch<T, A> hypot(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::hypot<A>(x, y, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the imaginary part of the batch \c x.
     * @param x batch of complex or real values.
     * @return the argument of \c x.
     */
    template <class T, class A>
    inline real_batch_type_t<batch<T, A>> imag(batch<T, A> const& x) noexcept
    {
        return kernel::imag<A>(x, A {});
    }

    /**
     * @ingroup batch_constant
     *
     * Return a batch of scalars representing positive infinity
     * @return a batch of positive infinity
     */
    template <class B>
    B infinity()
    {
        using T = typename B::value_type;
        return B(std::numeric_limits<T>::infinity());
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Create a new batch equivalent to \c x but with element \c val set at position \c pos
     * @param x batch
     * @param val value to set
     * @param pos index of the updated slot
     * @return copy of \c x with position \c pos set to \c val
     */
    template <class T, class A, size_t I>
    inline batch<T, A> insert(batch<T, A> const& x, T val, index<I> pos) noexcept
    {
        return kernel::insert<A>(x, val, pos, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Determines if the scalars in the given batch \c x represent an even integer value
     * @param x batch of floating point values.
     * @return a batch of booleans.
     */
    template <class T, class A>
    inline batch_bool<T, A> is_even(batch<T, A> const& x) noexcept
    {
        return kernel::is_even<A>(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Determines if the floating-point scalars in the given batch \c x represent integer value
     * @param x batch of floating point values.
     * @return a batch of booleans.
     */
    template <class T, class A>
    inline batch_bool<T, A> is_flint(batch<T, A> const& x) noexcept
    {
        return kernel::is_flint<A>(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Determines if the scalars in the given batch \c x represent an odd integer value
     * @param x batch of floating point values.
     * @return a batch of booleans.
     */
    template <class T, class A>
    inline batch_bool<T, A> is_odd(batch<T, A> const& x) noexcept
    {
        return kernel::is_odd<A>(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Determines if the scalars in the given batch \c x are inf values.
     * @param x batch of floating point values.
     * @return a batch of booleans.
     */
    template <class T, class A>
    inline batch_bool<T, A> isinf(batch<T, A> const& x) noexcept
    {
        return kernel::isinf<A>(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Determines if the scalars in the given batch \c x are finite values.
     * @param x batch of floating point values.
     * @return a batch of booleans.
     */
    template <class T, class A>
    inline batch_bool<T, A> isfinite(batch<T, A> const& x) noexcept
    {
        return kernel::isfinite<A>(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Determines if the scalars in the given batch \c x are NaN values.
     * @param x batch of floating point values.
     * @return a batch of booleans.
     */
    template <class T, class A>
    inline typename batch<T, A>::batch_bool_type isnan(batch<T, A> const& x) noexcept
    {
        return kernel::isnan<A>(x, A {});
    }

    /**
     * @ingroup batch_math_extra
     *
     * Computes the multiplication of the floating- point number x by 2 raised to the power exp.
     * @param x batch of floating point values.
     * @param y batch of floating point values.
     * @return the natural logarithm of the gamma function of \c x.
     */
    template <class T, class A>
    inline batch<T, A> ldexp(const batch<T, A>& x, const batch<as_integer_t<T>, A>& y) noexcept
    {
        return kernel::ldexp<A>(x, y, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Element-wise lesser or equal to comparison of batches \c x and \c y.
     * @param x batch involved in the comparison.
     * @param y batch involved in the comparison.
     * @return a boolean batch.
     */
    template <class T, class A>
    inline batch_bool<T, A> le(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return x <= y;
    }

    /**
     * @ingroup batch_math_extra
     *
     * Computes the natural logarithm of the gamma function of the batch \c x.
     * @param x batch of floating point values.
     * @return the natural logarithm of the gamma function of \c x.
     */
    template <class T, class A>
    inline batch<T, A> lgamma(batch<T, A> const& x) noexcept
    {
        return kernel::lgamma<A>(x, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the buffer \c ptr and the specifed
     * batch value type \c To. The memory needs to be aligned.
     * @param ptr the memory buffer to read
     * @return a new batch instance
     */
    template <class To, class A = default_arch, class From>
    inline simd_return_type<From, To> load_as(From const* ptr, aligned_mode) noexcept
    {
        using batch_value_type = typename simd_return_type<From, To>::value_type;
        return kernel::load_aligned<A>(ptr, kernel::convert<batch_value_type> {}, A {});
    }

    template <class To, class A = default_arch>
    inline simd_return_type<bool, To> load_as(bool const* ptr, aligned_mode) noexcept
    {
        return simd_return_type<bool, To>::load_aligned(ptr);
    }

    template <class To, class A = default_arch, class From>
    inline simd_return_type<std::complex<From>, To> load_as(std::complex<From> const* ptr, aligned_mode) noexcept
    {
        using batch_value_type = typename simd_return_type<std::complex<From>, To>::value_type;
        return kernel::load_complex_aligned<A>(ptr, kernel::convert<batch_value_type> {}, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the buffer \c ptr and the specifed
     * batch value type \c To. The memory does not need to be aligned.
     * @param ptr the memory buffer to read
     * @return a new batch instance
     */
    template <class To, class A = default_arch, class From>
    inline simd_return_type<From, To> load_as(From const* ptr, unaligned_mode) noexcept
    {
        using batch_value_type = typename simd_return_type<From, To>::value_type;
        return kernel::load_unaligned<A>(ptr, kernel::convert<batch_value_type> {}, A {});
    }

    template <class To, class A = default_arch>
    inline simd_return_type<bool, To> load_as(bool const* ptr, unaligned_mode) noexcept
    {
        return simd_return_type<bool, To>::load_unaligned(ptr);
    }

    template <class To, class A = default_arch, class From>
    inline simd_return_type<std::complex<From>, To> load_as(std::complex<From> const* ptr, unaligned_mode) noexcept
    {
        using batch_value_type = typename simd_return_type<std::complex<From>, To>::value_type;
        return kernel::load_complex_unaligned<A>(ptr, kernel::convert<batch_value_type> {}, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the buffer \c ptr. The
     * memory needs to be aligned.
     * @param ptr the memory buffer to read
     * @return a new batch instance
     */
    template <class A = default_arch, class From>
    inline batch<From, A> load(From const* ptr, aligned_mode = {}) noexcept
    {
        return load_as<From, A>(ptr, aligned_mode {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the buffer \c ptr. The
     * memory does not need to be aligned.
     * @param ptr the memory buffer to read
     * @return a new batch instance
     */
    template <class A = default_arch, class From>
    inline batch<From, A> load(From const* ptr, unaligned_mode) noexcept
    {
        return load_as<From, A>(ptr, unaligned_mode {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the buffer \c ptr. The
     * memory needs to be aligned.
     * @param ptr the memory buffer to read
     * @return a new batch instance
     */
    template <class A = default_arch, class From>
    inline batch<From, A> load_aligned(From const* ptr) noexcept
    {
        return load_as<From, A>(ptr, aligned_mode {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Creates a batch from the buffer \c ptr. The
     * memory does not need to be aligned.
     * @param ptr the memory buffer to read
     * @return a new batch instance
     */
    template <class A = default_arch, class From>
    inline batch<From, A> load_unaligned(From const* ptr) noexcept
    {
        return load_as<From, A>(ptr, unaligned_mode {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the natural logarithm of the batch \c x.
     * @param x batch of floating point values.
     * @return the natural logarithm of \c x.
     */
    template <class T, class A>
    inline batch<T, A> log(batch<T, A> const& x) noexcept
    {
        return kernel::log<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     * Computes the base 2 logarithm of the batch \c x.
     * @param x batch of floating point values.
     * @return the base 2 logarithm of \c x.
     */
    template <class T, class A>
    inline batch<T, A> log2(batch<T, A> const& x) noexcept
    {
        return kernel::log2<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     * Computes the base 10 logarithm of the batch \c x.
     * @param x batch of floating point values.
     * @return the base 10 logarithm of \c x.
     */
    template <class T, class A>
    inline batch<T, A> log10(batch<T, A> const& x) noexcept
    {
        return kernel::log10<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     * Computes the natural logarithm of one plus the batch \c x.
     * @param x batch of floating point values.
     * @return the natural logarithm of one plus \c x.
     */
    template <class T, class A>
    inline batch<T, A> log1p(batch<T, A> const& x) noexcept
    {
        return kernel::log1p<A>(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Element-wise lesser than comparison of batches \c x and \c y.
     * @param x batch involved in the comparison.
     * @param y batch involved in the comparison.
     * @return a boolean batch.
     */
    template <class T, class A>
    inline batch_bool<T, A> lt(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return x < y;
    }

    /**
     * @ingroup batch_math
     *
     * Computes the larger values of the batches \c x and \c y.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @return a batch of the larger values.
     */
    template <class T, class A>
    inline batch<T, A> max(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::max<A>(x, y, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the smaller values of the batches \c x and \c y.
     * @param x a batch of integer or floating point values.
     * @param y a batch of integer or floating point values.
     * @return a batch of the smaller values.
     */
    template <class T, class A>
    inline batch<T, A> min(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::min<A>(x, y, A {});
    }

    /**
     * @ingroup batch_constant
     *
     * Return a batch of scalars representing positive infinity
     * @return a batch of positive infinity
     */
    template <class B>
    inline B minusinfinity() noexcept
    {
        using T = typename B::value_type;
        return B(-std::numeric_limits<T>::infinity());
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the integer modulo of the batch \c x by the batch \c y.
     * @param x batch involved in the modulo.
     * @param y batch involved in the modulo.
     * @return the result of the modulo.
     */
    template <class T, class Tp>
    inline auto mod(T const& x, Tp const& y) noexcept -> decltype(x % y)
    {
        return x % y;
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the product of the batches \c x and \c y.
     * @tparam X the actual type of batch.
     * @param x batch involved in the product.
     * @param y batch involved in the product.
     * @return the result of the product.
     */
    template <class T, class Tp>
    inline auto mul(T const& x, Tp const& y) noexcept -> decltype(x * y)
    {
        return x * y;
    }

    /**
     * @ingroup batch_rounding
     *
     * Rounds the scalars in \c x to integer values (in floating point format), using
     * the current rounding mode.
     * @param x batch of floating point values.
     * @return the batch of nearest integer values.
     */
    template <class T, class A>
    inline batch<T, A> nearbyint(batch<T, A> const& x) noexcept
    {
        return kernel::nearbyint<A>(x, A {});
    }

    /**
     * @ingroup batch_rounding
     *
     * Rounds the scalars in \c x to integer values (in integer format) using
     * the current rounding mode.
     * @param x batch of floating point values.
     * @return the batch of nearest integer values.
     */
    template <class T, class A>
    inline batch<as_integer_t<T>, A>
    nearbyint_as_int(batch<T, A> const& x) noexcept
    {
        return kernel::nearbyint_as_int(x, A {});
    }

    /**
     * @ingroup batch_logical
     *
     * Element-wise inequality comparison of batches \c x and \c y.
     * @param x batch involved in the comparison.
     * @param y batch involved in the comparison.
     * @return a boolean batch.
     */
    template <class T, class A>
    inline batch_bool<T, A> neq(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return x != y;
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the opposite of the batch \c x.
     * @param x batch involved in the operation.
     * @return the opposite of \c x.
     */
    template <class T, class A>
    inline batch<T, A> neg(batch<T, A> const& x) noexcept
    {
        return -x;
    }

    /**
     * @ingroup batch_math_extra
     *
     * Computes  the next representable  floating-point
     *        value  following  x  in the direction of y
     * @param x batch of floating point values.
     * @param y batch of floating point values.
     * @return \c x raised to the power \c y.
     */
    template <class T, class A>
    inline batch<T, A> nextafter(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::nextafter<A>(x, y, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the norm of the batch \c x.
     * @param x batch of complex or real values.
     * @return the norm of \c x.
     */
    template <class A, class T>
    inline real_batch_type_t<batch<T, A>> norm(batch<T, A> const& x) noexcept
    {
        return kernel::norm(x, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * No-op on \c x.
     * @param x batch involved in the operation.
     * @return \c x.
     */
    template <class T, class A>
    inline batch<T, A> pos(batch<T, A> const& x) noexcept
    {
        return +x;
    }

    /**
     * @ingroup batch_math
     *
     * Computes the value of the batch \c x raised to the power
     * \c y.
     * @param x batch of floating point values.
     * @param y batch of floating point values.
     * @return \c x raised to the power \c y.
     */
    template <class T, class A>
    inline batch<T, A> pow(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::pow<A>(x, y, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the value of the batch \c x raised to the power
     * \c y.
     * @param x batch of integral values.
     * @param y batch of integral values.
     * @return \c x raised to the power \c y.
     */
    template <class T, class ITy, class A, class = typename std::enable_if<std::is_integral<ITy>::value, void>::type>
    inline batch<T, A> pow(batch<T, A> const& x, ITy y) noexcept
    {
        return kernel::ipow<A>(x, y, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the projection of the batch \c z.
     * @param z batch of complex or real values.
     * @return the projection of \c z.
     */
    template <class A, class T>
    inline complex_batch_type_t<batch<T, A>> proj(batch<T, A> const& z) noexcept
    {
        return kernel::proj(z, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the approximate reciprocal of the batch \c x.
     * The maximum relative error for this approximation is
     * less than 1.5*2^-12.
     * @param x batch of floating point numbers.
     * @return the reciprocal.
     */
    template <class T, class A, class = typename std::enable_if<std::is_floating_point<T>::value, void>::type>
    inline batch<T, A> reciprocal(batch<T, A> const& x) noexcept
    {
        return kernel::reciprocal(x, A {});
    }

    /**
     * @ingroup batch_complex
     *
     * Computes the real part of the batch \c z.
     * @param z batch of complex or real values.
     * @return the argument of \c z.
     */
    template <class T, class A>
    inline real_batch_type_t<batch<T, A>> real(batch<T, A> const& z) noexcept
    {
        return kernel::real<A>(z, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the remainder of dividing \c x by \c y
     * @param x batch of scalar values
     * @param y batch of scalar values
     * @return the result of the addition.
     */
    template <class T, class A>
    inline batch<T, A> remainder(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::remainder<A>(x, y, A {});
    }

    /**
     * @ingroup batch_rounding
     *
     * Rounds the scalars in \c x to integer values (in floating point format), using
     * the current rounding mode.
     * @param x batch of floating point values.
     * @return the batch of rounded values.
     */
    template <class T, class A>
    inline batch<T, A> rint(batch<T, A> const& x) noexcept
    {
        return nearbyint(x);
    }

    /**
     * @ingroup batch_rounding
     *
     * Computes the batch of nearest integer values to scalars in \c x (in
     * floating point format), rounding halfway cases away from zero, regardless
     * of the current rounding mode.
     * @param x batch of flaoting point values.
     * @return the batch of nearest integer values.
     */
    template <class T, class A>
    inline batch<T, A> round(batch<T, A> const& x) noexcept
    {
        return kernel::round<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the inverse square root of the batch \c x.
     * @param x batch of floating point values.
     * @return the inverse square root of \c x.
     */
    template <class T, class A>
    inline batch<T, A> rsqrt(batch<T, A> const& x) noexcept
    {
        return kernel::rsqrt<A>(x, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the saturate sum of the batch \c x and the batch \c y.
     * \c x.
     * @tparam X the actual type of batch.
     * @param x batch involved in the saturated addition.
     * @param y batch involved in the saturated addition.
     * @return the result of the saturated addition.
     */
    template <class T, class Tp>
    inline auto sadd(T const& x, Tp const& y) noexcept -> decltype(x + y)
    {
        using B = decltype(x + y);
        using A = typename B::arch_type;
        return kernel::sadd<A>(B(x), B(y), A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Ternary operator for batches: selects values from the batches \c true_br or \c false_br
     * depending on the boolean values in the constant batch \c cond. Equivalent to
     * \code{.cpp}
     * for(std::size_t i = 0; i < N; ++i)
     *     res[i] = cond[i] ? true_br[i] : false_br[i];
     * \endcode
     * @param cond constant batch condition.
     * @param true_br batch values for truthy condition.
     * @param false_br batch value for falsy condition.
     * @return the result of the selection.
     */
    template <class T, class A>
    inline batch<T, A> select(batch_bool<T, A> const& cond, batch<T, A> const& true_br, batch<T, A> const& false_br) noexcept
    {
        return kernel::select<A>(cond, true_br, false_br, A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Ternary operator for batches: selects values from the batches \c true_br or \c false_br
     * depending on the boolean values in the constant batch \c cond. Equivalent to
     * \code{.cpp}
     * for(std::size_t i = 0; i < N; ++i)
     *     res[i] = cond[i] ? true_br[i] : false_br[i];
     * \endcode
     * @param cond constant batch condition.
     * @param true_br batch values for truthy condition.
     * @param false_br batch value for falsy condition.
     * @return the result of the selection.
     */
    template <class T, class A>
    inline batch<std::complex<T>, A> select(batch_bool<T, A> const& cond, batch<std::complex<T>, A> const& true_br, batch<std::complex<T>, A> const& false_br) noexcept
    {
        return kernel::select<A>(cond, true_br, false_br, A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Ternary operator for batches: selects values from the batches \c true_br or \c false_br
     * depending on the boolean values in the constant batch \c cond. Equivalent to
     * \code{.cpp}
     * for(std::size_t i = 0; i < N; ++i)
     *     res[i] = cond[i] ? true_br[i] : false_br[i];
     * \endcode
     * @param cond constant batch condition.
     * @param true_br batch values for truthy condition.
     * @param false_br batch value for falsy condition.
     * @return the result of the selection.
     */
    template <class T, class A, bool... Values>
    inline batch<T, A> select(batch_bool_constant<batch<T, A>, Values...> const& cond, batch<T, A> const& true_br, batch<T, A> const& false_br) noexcept
    {
        return kernel::select<A>(cond, true_br, false_br, A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Computes the sign of \c x
     * @param x batch
     * @return -1 for each negative element, -1 or +1 for each null element and +1 for each element
     */
    template <class T, class A>
    inline batch<T, A> sign(batch<T, A> const& x) noexcept
    {
        return kernel::sign<A>(x, A {});
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Computes the sign of \c x, assuming x doesn't have any zero
     * @param x batch
     * @return -1 for each negative element, -1 or +1 for each null element and +1 for each element
     */
    template <class T, class A>
    inline batch<T, A> signnz(batch<T, A> const& x) noexcept
    {
        return kernel::signnz<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the sine of the batch \c x.
     * @param x batch of floating point values.
     * @return the sine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> sin(batch<T, A> const& x) noexcept
    {
        return kernel::sin<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the hyperbolic sine of the batch \c x.
     * @param x batch of floating point values.
     * @return the hyperbolic sine of \c x.
     */
    template <class T, class A>
    inline batch<T, A> sinh(batch<T, A> const& x) noexcept
    {
        return kernel::sinh<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the sine and the cosine of the batch \c x. This method is faster
     * than calling sine and cosine independently.
     * @param x batch of floating point values.
     * @return a pair containing the sine then the cosine of  batch \c x
     */
    template <class T, class A>
    inline std::pair<batch<T, A>, batch<T, A>> sincos(batch<T, A> const& x) noexcept
    {
        return kernel::sincos<A>(x, A {});
    }

    /**
     * @ingroup batch_math
     *
     * Computes the square root of the batch \c x.
     * @param x batch of floating point values.
     * @return the square root of \c x.
     */
    template <class T, class A>
    inline batch<T, A> sqrt(batch<T, A> const& x) noexcept
    {
        return kernel::sqrt<A>(x, A {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the saturate difference of the batch \c x and the batch \c y.
     * \c x.
     * @tparam X the actual type of batch.
     * @param x batch involved in the saturated difference.
     * @param y batch involved in the saturated difference.
     * @return the result of the saturated difference.
     */
    template <class T, class Tp>
    inline auto ssub(T const& x, Tp const& y) noexcept -> decltype(x - y)
    {
        using B = decltype(x + y);
        using A = typename B::arch_type;
        return kernel::ssub<A>(B(x), B(y), A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Copy content of batch \c src to the buffer \c dst. The
     * memory needs to be aligned.
     * @param dst the memory buffer to write to
     * @param src the batch to copy
     */
    template <class To, class A = default_arch, class From>
    inline void store_as(To* dst, batch<From, A> const& src, aligned_mode) noexcept
    {
        kernel::store_aligned(dst, src, A {});
    }

    template <class A = default_arch, class From>
    inline void store_as(bool* dst, batch_bool<From, A> const& src, aligned_mode) noexcept
    {
        kernel::store(src, dst, A {});
    }

    template <class To, class A = default_arch, class From>
    inline void store_as(std::complex<To>* dst, batch<std::complex<From>, A> const& src, aligned_mode) noexcept
    {
        kernel::store_complex_aligned(dst, src, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Copy content of batch \c src to the buffer \c dst. The
     * memory does not need to be aligned.
     * @param dst the memory buffer to write to
     * @param src the batch to copy
     */
    template <class To, class A = default_arch, class From>
    inline void store_as(To* dst, batch<From, A> const& src, unaligned_mode) noexcept
    {
        kernel::store_unaligned(dst, src, A {});
    }

    template <class A = default_arch, class From>
    inline void store_as(bool* dst, batch_bool<From, A> const& src, unaligned_mode) noexcept
    {
        kernel::store(src, dst, A {});
    }

    template <class To, class A = default_arch, class From>
    inline void store_as(std::complex<To>* dst, batch<std::complex<From>, A> const& src, unaligned_mode) noexcept
    {
        kernel::store_complex_unaligned(dst, src, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Copy content of batch \c val to the buffer \c mem. The
     * memory does not need to be aligned.
     * @param mem the memory buffer to write to
     * @param val the batch to copy from
     */
    template <class A, class T>
    inline void store(T* mem, batch<T, A> const& val, aligned_mode = {}) noexcept
    {
        store_as<T, A>(mem, val, aligned_mode {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Copy content of batch \c val to the buffer \c mem. The
     * memory does not need to be aligned.
     * @param mem the memory buffer to write to
     * @param val the batch to copy from
     */
    template <class A, class T>
    inline void store(T* mem, batch<T, A> const& val, unaligned_mode) noexcept
    {
        store_as<T, A>(mem, val, unaligned_mode {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Copy content of batch \c val to the buffer \c mem. The
     * memory needs to be aligned.
     * @param mem the memory buffer to write to
     * @param val the batch to copy from
     */
    template <class A, class T>
    inline void store_aligned(T* mem, batch<T, A> const& val) noexcept
    {
        store_as<T, A>(mem, val, aligned_mode {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Copy content of batch \c val to the buffer \c mem. The
     * memory does not need to be aligned.
     * @param mem the memory buffer to write to
     * @param val the batch to copy
     */
    template <class A, class T>
    inline void store_unaligned(T* mem, batch<T, A> const& val) noexcept
    {
        store_as<T, A>(mem, val, unaligned_mode {});
    }

    /**
     * @ingroup batch_arithmetic
     *
     * Computes the difference between \c x and \c y
     * @tparam X the actual type of batch.
     * @param x scalar or batch of scalars
     * @param y scalar or batch of scalars
     * @return the difference between \c x and \c y
     */
    template <class T, class Tp>
    inline auto sub(T const& x, Tp const& y) noexcept -> decltype(x - y)
    {
        return x - y;
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Rearrange elements from \c x according to mask \c mask
     * @param x batch
     * @param mask constant batch mask of integer elements of the same size as
     * element of \c x
     * @return swizzled batch
     */
    template <class T, class A, class Vt, Vt... Values>
    inline typename std::enable_if<std::is_arithmetic<T>::value, batch<T, A>>::type
    swizzle(batch<T, A> const& x, batch_constant<batch<Vt, A>, Values...> mask) noexcept
    {
        static_assert(sizeof(T) == sizeof(Vt), "consistent mask");
        return kernel::swizzle<A>(x, mask, A {});
    }
    template <class T, class A, class Vt, Vt... Values>
    inline batch<std::complex<T>, A> swizzle(batch<std::complex<T>, A> const& x, batch_constant<batch<Vt, A>, Values...> mask) noexcept
    {
        static_assert(sizeof(T) == sizeof(Vt), "consistent mask");
        return kernel::swizzle<A>(x, mask, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the tangent of the batch \c x.
     * @param x batch of floating point values.
     * @return the tangent of \c x.
     */
    template <class T, class A>
    inline batch<T, A> tan(batch<T, A> const& x) noexcept
    {
        return kernel::tan<A>(x, A {});
    }

    /**
     * @ingroup batch_trigo
     *
     * Computes the hyperbolic tangent of the batch \c x.
     * @param x batch of floating point values.
     * @return the hyperbolic tangent of \c x.
     */
    template <class T, class A>
    inline batch<T, A> tanh(batch<T, A> const& x) noexcept
    {
        return kernel::tanh<A>(x, A {});
    }

    /**
     * @ingroup batch_math_extra
     *
     * Computes the gamma function of the batch \c x.
     * @param x batch of floating point values.
     * @return the gamma function of \c x.
     */
    template <class T, class A>
    inline batch<T, A> tgamma(batch<T, A> const& x) noexcept
    {
        return kernel::tgamma<A>(x, A {});
    }

    /**
     * @ingroup batch_conversion
     *
     * Perform a conversion from \c i to a value of an floating point type of the same size as \c T.
     * This is equivalent to \c batch_cast<as_float_t<T>>(i)
     * @param i batch of integers.
     * @return \c i converted to a value of an floating point type of the same size as \c T
     */
    template <class T, class A>
    inline batch<as_float_t<T>, A> to_float(batch<T, A> const& i) noexcept
    {
        return batch_cast<as_float_t<T>>(i);
    }

    /**
     * @ingroup batch_conversion
     *
     * Perform a conversion from \c x to a value of an integer type of the same size as \c T
     * This is equivalent to \c batch_cast<as_integer_t<T>>(x)
     * @param x batch.
     * @return \c x converted to a value of an integer type of the same size as \c T
     */
    template <class T, class A>
    inline batch<as_integer_t<T>, A> to_int(batch<T, A> const& x) noexcept
    {
        return batch_cast<as_integer_t<T>>(x);
    }

    /**
     * @ingroup batch_rounding
     *
     * Computes the batch of nearest integer values not greater in magnitude
     * than scalars in \c x.
     * @param x batch of floating point values.
     * @return the batch of nearest integer values not greater in magnitude than \c x.
     */
    template <class T, class A>
    inline batch<T, A> trunc(batch<T, A> const& x) noexcept
    {
        return kernel::trunc<A>(x, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Unpack and interleave data from the HIGH half of batches \c x and \c y.
     * Store the results in the Return value.
     * @param x a batch of integer or floating point or double precision values.
     * @param y a batch of integer or floating point or double precision values.
     * @return a batch of the high part of shuffled values.
     */
    template <class T, class A>
    inline batch<T, A> zip_hi(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::zip_hi<A>(x, y, A {});
    }

    /**
     * @ingroup batch_data_transfer
     *
     * Unpack and interleave data from the LOW half of batches \c x and \c y.
     * Store the results in the Return value.
     * @param x a batch of integer or floating point or double precision values.
     * @param y a batch of integer or floating point or double precision values.
     * @return a batch of the low part of shuffled values.
     */
    template <class T, class A>
    inline batch<T, A> zip_lo(batch<T, A> const& x, batch<T, A> const& y) noexcept
    {
        return kernel::zip_lo<A>(x, y, A {});
    }

    // bitwise_cast
    template <class A, class T, typename std::enable_if<std::is_integral<T>::value, int>::type = 3>
    inline batch<T, A> bitwise_cast(batch_bool<T, A> const& self) noexcept
    {
        T z(0);
        return select(self, batch<T, A>(T(~z)), batch<T, A>(z));
    }

    template <class A, class T, typename std::enable_if<std::is_floating_point<T>::value, int>::type = 3>
    inline batch<T, A> bitwise_cast(batch_bool<T, A> const& self) noexcept
    {
        T z0(0), z1(0);
        using int_type = as_unsigned_integer_t<T>;
        int_type value(~int_type(0));
        std::memcpy(&z1, &value, sizeof(int_type));
        return select(self, batch<T, A>(z1), batch<T, A>(z0));
    }

    /**
     * @ingroup batch_bool_reducers
     *
     * Returns true if all the boolean values in the batch are true,
     * false otherwise.
     * @param x the batch to reduce.
     * @return a boolean scalar.
     */
    template <class T, class A>
    inline bool all(batch_bool<T, A> const& x) noexcept
    {
        return kernel::all<A>(x, A {});
    }

    /**
     * @ingroup batch_bool_reducers
     *
     * Return true if any of the boolean values in the batch is true,
     * false otherwise.
     * @param x the batch to reduce.
     * @return a boolean scalar.
     */
    template <class T, class A>
    inline bool any(batch_bool<T, A> const& x) noexcept
    {
        return kernel::any<A>(x, A {});
    }

    /**
     * @ingroup batch_bool_reducers
     *
     * Return true if none of the boolean values in the batch is true,
     * false otherwise.
     * @param x the batch to reduce.
     * @return a boolean scalar.
     */
    template <class T, class A>
    inline bool none(batch_bool<T, A> const& x) noexcept
    {
        return !xsimd::any(x);
    }

    /**
     * @ingroup batch_miscellaneous
     *
     * Dump the content of batch \c x to stream \c o
     * @param o the stream where the batch is dumped
     * @param x batch to dump.
     * @return a reference to \c o
     */
    template <class T, class A>
    inline std::ostream& operator<<(std::ostream& o, batch<T, A> const& x) noexcept
    {
        constexpr auto size = batch<T, A>::size;
        alignas(A::alignment()) T buffer[size];
        x.store_aligned(&buffer[0]);
        o << '(';
        for (std::size_t i = 0; i < size - 1; ++i)
            o << buffer[i] << ", ";
        return o << buffer[size - 1] << ')';
    }
}

#endif
