Additional operator overloads

Issue #255 resolved
Matthias Moulin created an issue
It would be covenient to have operator overloads for:

// Comparison operators
V(X) ==  V(X) -> V(bool)
V(X) !=  V(X) -> V(bool)
V(X) <   V(X) -> V(bool)
V(X) <=  V(X) -> V(bool)
V(X) >   V(X) -> V(bool)
V(X) >=  V(X) -> V(bool)
V(X) <=> V(X) -> V(strong_ordering)

any(V(bool)) -> bool
all(V(bool)) -> bool

// Logical operators
V(bool) && V(bool) -> V(bool)
V(bool) || V(bool) -> V(bool)
!V(bool)           -> V(bool)

// Arithmetic operators
V(unsigned integral) &  V(unsigned integral) -> V(unsigned integral)
V(unsigned integral) |  V(unsigned integral) -> V(unsigned integral)
V(unsigned integral) ~  V(unsigned integral) -> V(unsigned integral)
V(unsigned integral) << V(unsigned integral) -> V(unsigned integral)
V(unsigned integral) >> V(unsigned integral) -> V(unsigned integral)
V(unsigned integral) <<   unsigned integral  -> V(unsigned integral)
V(unsigned integral) >>   unsigned integral  -> V(unsigned integral)

// Assignment operators
V(unsigned integral)  &= V(unsigned integral) -> V(unsigned integral)&
V(unsigned integral)  |= V(unsigned integral) -> V(unsigned integral)&
V(unsigned integral)  ~= V(unsigned integral) -> V(unsigned integral)&
V(unsigned integral) <<= V(unsigned integral) -> V(unsigned integral)&
V(unsigned integral) >>= V(unsigned integral) -> V(unsigned integral)&
V(unsigned integral) <<=   unsigned integral  -> V(unsigned integral)&
V(unsigned integral) >>=   unsigned integral  -> V(unsigned integral)&

Here V can be a vector or matrix type. Having these operators would make Blaze very suitable as a HLSL/GLSL equivalent on the CPU side while still having one SIMD math library supporting arbitrary vector and matrix dimensions (<> GLM, DirectXMath).

Note that the operators involving scalars are also related to #70, #109 and #132.

Kind regards,

Matthias

Comments (13)

  1. Klaus Iglberger

    Hi Matthias!

    Thanks a lot for the great proposal. We agree that this would be a great addition to Blaze and we will try to integrate this as soon as possible. Until then you can build on the map() function to implement these operators yourself:

    Logical operators

    template< typename VT, bool TF >
    decltype(auto) operator!( const DenseVector<VT,TF>& vec )
    {
       return map( ~vec, []( auto a ){ return !a; } );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator&&( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    {
       return map( ~lhs, ~rhs, []( auto a, auto b ){ return a && b; } );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator||( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    {
       return map( ~lhs, ~rhs, []( auto a, auto b ){ return a || b; } );
    }
    

    Arithmetic operators

    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator&( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    {
       return map( ~lhs, ~rhs, []( auto a, auto b ){ return a & b; } );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator|( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    {
       return map( ~lhs, ~rhs, []( auto a, auto b ){ return a | b; } );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator<<( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    {
       return map( ~lhs, ~rhs, []( auto a, auto b ){ return a << b; } );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator>>( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    {
       return map( ~lhs, ~rhs, []( auto a, auto b ){ return a >> b; } );
    }
    
    template< typename VT, bool TF, typename ST, EnableIf_t< IsIntegral_v<ST> >* = nullptr >
    decltype(auto) operator<<( const DenseVector<VT,TF>& vec, ST scalar )
    {
       return map( ~vec, [scalar]( auto a ){ return a << scalar; } );
    }
    
    template< typename VT, bool TF, typename ST, EnableIf_t< IsIntegral_v<ST> >* = nullptr >
    decltype(auto) operator>>( const DenseVector<VT,TF>& vec, ST scalar )
    {
       return map( ~vec, [scalar]( auto a ){ return a >> scalar; } );
    }
    

    Although this implementation works, it depends on the compiler to vectorise the operation. In order to guarantee vectorization you can implement custom operations. For instance, the following struct implements the necessary right-shift operation for the last operator>>() (please not that it is limited to 32-bit integrals and AVX2):

    struct ShiftRight
    {
     public:
       explicit inline ShiftRight( int count )
          : count_( count )
       {}
    
       template< typename T >
       decltype(auto) operator()( const T& a ) const
       {
          return a >> count_;
       }
    
       template< typename T >
       static constexpr bool simdEnabled() { return BLAZE_AVX2_MODE; }
    
       static constexpr bool paddingEnabled() { return true; }
    
       template< typename T >
       decltype(auto) load( const T& a ) const
       {
          BLAZE_CONSTRAINT_MUST_BE_SIMD_PACK( T );
          return blaze::SIMDuint32( _mm256_srli_epi32( a.value, count_ ) );
       }
    
     private:
       int count_;
    };
    

    This struct can be used to upgrade the operator>>():

    template< typename VT, bool TF, typename ST, EnableIf_t< IsIntegral_v<ST> >* = nullptr >
    decltype(auto) operator>>( const DenseVector<VT,TF>& vec, ST scalar )
    {
       return map( ~vec, ShiftRight( scalar ) );
    }
    

    Example:

    blaze::DynamicVector<unsigned int> a{ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };
    
    blaze::DynamicVector<unsigned int> b( a >> 1 );  // Results in ( 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 )
    

    Thanks again for the proposal,

    Best regards,

    Klaus!

  2. Klaus Iglberger

    Hi Matthias!

    With the last push we have provided the majority of the requested operators. All arithmetic operators (i.e. operator&(), operator|(), operator^(), operator<<(), and operator>>()) and logical operators (i.e. operator!(), operator&&(), and operator||()) can now be used for both dense vectors and dense matrices.

    We are still thinking about the relational operators and the proposed any() and all() functions, though. Since operator==() already exists, there is unfortunately no way to provide another operator==() for an elementwise comparison. We'll have to figure out in which form we could provide these operations or if a user has to manually define these operations by means of the map() function.

    Best regards,

    Klaus!

  3. Matthias Moulin reporter

    Thanks Klaus! This is a great addition to the library.

    Imo, the relational<, <=, >, >= operators make mathematically only sense if performed elementwise. Unfortunately, == and!= make both sense if performed elementwise and if performed elementwise + collapse using all or any, respectively. The latter is even more unfortunate as that eliminates the possibility of an implicit cast operator from V(bool)to bool .

    What is the current use case of ==?

  4. Matthias Moulin reporter

    Another interesting addition and closely associated to the given list of operators is a function to represent the ternary ?: operator, which unfortunately could not be overloaded in C++, but naturally translates to SIMD intrinsics using a mask (with the difference that short circuiting is not possible). (Note that short circuiting is also not possible for overloads of operator && and ||, but that is never really a use case for an algebra library).

    Select(V(bool), V(X), V(X));

    Pseudocode:

    Result[i] = (V1[i] & mask[i])|(V2[i] & ~mask[i]);

    It is possible to implement that already using map and the operator overloads or using intrinsics:

    SSE: _mm_and_ps, _mm_andnot_ps, _mm_or_ps

    ARM NEON: vbslq_f32

  5. Klaus Iglberger

    Hi Matthias!

    I agree with your assessment about the less-than and greater-than comparisons. The current use case for the comparison operator is a complete comparison of two vectors or matrices (i.e. “Are these two equal?”). Since this behavior has been part of Blaze since version 1.0, this is something that cannot be changed anymore.

    Unfortunately it is not possible to use the map() function for emulating the conditional operator since map() only takes two arguments. But I really like the idea of a select() function. I believe that in the context of this issue this would be too much, but I would appreciate another issue with a specific request for this feature.

    Best regards,

    Klaus!

  6. Klaus Iglberger

    Hi Matthias!

    For the release of Blaze 3.6 we have decided to leave the relational operators out. However, with the latest additions to Blaze they are very easily manually implemented. The following code snippet provides a blueprint for how to implement operator<(), operator>(), operator<=() and operator>=() for both dense vectors and dense matrices. For the implementation of all() and any() we recommend to build on std::all_of() and std::any_of() since these functions exploit short circuit evaluation.

    Relational Operators

    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator<( const blaze::DenseVector<VT1,TF>& lhs, const blaze::DenseVector<VT2,TF>& rhs )
    {
       return blaze::map( ~lhs, ~rhs, blaze::Less{} );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator>( const blaze::DenseVector<VT1,TF>& lhs, const blaze::DenseVector<VT2,TF>& rhs )
    {
       return blaze::map( ~lhs, ~rhs, blaze::Greater{} );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator<=( const blaze::DenseVector<VT1,TF>& lhs, const blaze::DenseVector<VT2,TF>& rhs )
    {
       return !( ~lhs > ~rhs );
    }
    
    template< typename VT1, typename VT2, bool TF >
    decltype(auto) operator>=( const blaze::DenseVector<VT1,TF>& lhs, const blaze::DenseVector<VT2,TF>& rhs )
    {
       return !( ~lhs < ~rhs );
    }
    
    template< typename MT1, bool SO1, typename MT2, bool SO2 >
    decltype(auto) operator<( const blaze::DenseMatrix<MT1,SO1>& lhs, const blaze::DenseMatrix<MT2,SO2>& rhs )
    {
       return blaze::map( ~lhs, ~rhs, blaze::Less{} );
    }
    
    template< typename MT1, bool SO1, typename MT2, bool SO2 >
    decltype(auto) operator>( const blaze::DenseMatrix<MT1,SO1>& lhs, const blaze::DenseMatrix<MT2,SO2>& rhs )
    {
       return blaze::map( ~lhs, ~rhs, blaze::Greater{} );
    }
    
    template< typename MT1, bool SO1, typename MT2, bool SO2 >
    decltype(auto) operator<=( const blaze::DenseMatrix<MT1,SO1>& lhs, const blaze::DenseMatrix<MT2,SO2>& rhs )
    {
       return !( ~lhs > ~rhs );
    }
    
    template< typename MT1, bool SO1, typename MT2, bool SO2 >
    decltype(auto) operator>=( const blaze::DenseMatrix<MT1,SO1>& lhs, const blaze::DenseMatrix<MT2,SO2>& rhs )
    {
       return !( ~lhs < ~rhs );
    }
    
    template< typename VT, bool TF >
    bool any( const blaze::DenseVector<VT,TF>& dv )
    {
       return std::any_of( begin(~dv), end(~dv), []( const auto& v ) -> bool { return v; } );
    }
    
    template< typename VT, bool TF >
    bool all( const blaze::DenseVector<VT,TF>& dv )
    {
       return std::all_of( begin(~dv), end(~dv), []( const auto& v ) -> bool { return v; } );
    }
    

    Thanks again for creating this issue,

    Best regards,

    Klaus!

  7. Klaus Iglberger

    Bitwise Shift

    Vector/Vector Shift

    Via the left-shift operator (i.e. operator<<()) and the right-shift operator (i.e. operator>>()) it is possible to perform an elementwise shift of a dense vector:

    blaze::DynamicVector<unsigned int> v1( 5UL ), v3;
    blaze::DynamicVector<unsigned short> v2( 5UL );
    
    // ... Initializing the vectors
    
    v3 = v1 << v2;  // Elementwise left-shift of a dense column vector
    v3 = v1 >> v2;  // Elementwise right-shift of a dense column vector
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. Also note that it is only possible to shift vectors with the same transpose flag:

    using blaze::columnVector;
    using blaze::rowVector;
    
    blaze::DynamicVector<unsigned int,columnVector> v1( 5UL );
    blaze::DynamicVector<unsigned int,rowVector>    v2( 5UL );
    
    v1 << v2;           // Compilation error: Cannot shift a column vector by a row vector
    v1 << trans( v2 );  // OK: Shifting a column vector by another column vector
    

    Furthermore, it is possible to use different element types in the two vector operands, but shifting two vectors with the same element type is favorable due to possible vectorization of the operation:

    blaze::DynamicVector<unsigned int> v1( 100UL ), v2( 100UL ), v3;
    
    // ... Initialization of the vectors
    
    v3 = v1 << v2;  // Vectorized left-shift of an unsigned int vector
    

    Matrix/Matrix Shift

    The left-shift operator (i.e. operator<<()) and the right-shift operator (i.e. operator>>()) can also be used to perform an elementwise shift of a dense matrix:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<unsigned int,columnMajor> M1( 7UL, 3UL );
    blaze::DynamicMatrix<unsigned short,rowMajor>  M2( 7UL, 3UL ), M3;
    
    // ... Initializing the matrices
    
    M3 = M1 << M2;  // Elementwise left-shift of a dense column-major matrix
    M3 = M1 >> M2;  // Elementwise right-shift of a dense column-major matrix
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. It is possible to use any combination of row-major and column-major matrices. Note however that in favor of performance using two matrices with the same storage order is favorable. The same argument holds for the element type: While it is possible to use matrices with different element type, using two matrices with the same element type potentially leads to better performance due to vectorization of the operation.

    blaze::DynamicMatrix<unsigned int> M1( 50UL, 70UL ), M2( 50UL, 70UL ), M3;
    
    // ... Initialization of the matrices
    
    M3 = M1 << M2;  // Vectorized left-shift of an unsigned int matrix
    

    Scalar Shift

    It is also possible to uniformly shift all elements of a dense vector or dense matrix by means of a scalar, which has the same effect as shifting by means of a uniform vector or matrix (see UniformVector and UniformMatrix). In Blaze it is possible to use all built-in/fundamental data types except bool as scalar values. Examples:

    blaze::DynamicVector<unsigned int> v1{ 3, 2, 5, 4, 1, 6 };
    
    // Uniform left-shift by one bit of all elements of v1; Results in
    //
    //    ( 6, 4, 10, 8, 2, 12 )
    //
    blaze::DynamicVector<int> v2( v1 << 1U );
    
    blaze::DynamicMatrix<unsigned int> M1{ { 3, 2, 5 },
                                           { 4, 1, 6 } };
    
    // Uniform left-shift by one bit of all elements of M1; Results in
    //
    //    ( 6, 4, 10 )
    //    ( 8, 2, 12 )
    //
    blaze::DynamicMatrix<unsigned int> M2( M1 << 1U );
    

    Bitwise AND

    Vector/Vector Bitwise AND

    Via the bitwise AND operator (i.e. operator&()) it is possible to perform an elementwise bitwise AND with dense vectors:

    blaze::DynamicVector<unsigned int> v1( 5UL ), v3;
    blaze::DynamicVector<unsigned short> v2( 5UL );
    
    // ... Initializing the vectors
    
    v3 = v1 & v2;  // Elementwise bitwise AND of two dense column vectors of different data type
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. Also note that it is only possible to use vectors with the same transpose flag:

    using blaze::columnVector;
    using blaze::rowVector;
    
    blaze::DynamicVector<unsigned int,columnVector> v1( 5UL );
    blaze::DynamicVector<unsigned int,rowVector>    v2( 5UL );
    
    v1 & v2;           // Compilation error: Cannot AND a column vector and a row vector
    v1 & trans( v2 );  // OK: Bitwise AND of two column vectors
    

    Furthermore, it is possible to use different element types in the two vector operands, but a bitwise AND of two vectors with the same element type is favorable due to possible vectorization of the operation:

    blaze::DynamicVector<unsigned int> v1( 100UL ), v2( 100UL ), v3;
    
    // ... Initialization of the vectors
    
    v3 = v1 & v2;  // Vectorized bitwise AND of an unsigned int vector
    

    Matrix/Matrix Bitwise AND

    The bitwise AND operator (i.e. operator&()) can also be used to perform an elementwise bitwise AND with dense matrices:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<unsigned int,columnMajor> M1( 7UL, 3UL );
    blaze::DynamicMatrix<unsigned short,rowMajor>  M2( 7UL, 3UL ), M3;
    
    // ... Initializing the matrices
    
    M3 = M1 & M2;  // Elementwise bitwise AND of two dense matrices of different data type
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. It is possible to use any combination of row-major and column-major matrices. Note however that in favor of performance using two matrices with the same storage order is favorable. The same argument holds for the element type: While it is possible to use matrices with different element type, using two matrices with the same element type potentially leads to better performance due to vectorization of the operation.

    blaze::DynamicMatrix<unsigned int> M1( 50UL, 70UL ), M2( 50UL, 70UL ), M3;
    
    // ... Initialization of the matrices
    
    M3 = M1 & M2;  // Vectorized bitwise AND of two row-major, unsigned int dense matrices
    

    Scalar Bitwise AND

    Is is also possible to perform a bitwise AND between a dense vector or dense matrix and a scalar value, which has the same effect as performing a bitwise AND by means of a uniform vector or matrix (see UniformVector and UniformMatrix). In Blaze it is possible to use all built-in/fundamental data types except bool as scalar values. Examples:

    blaze::DynamicVector<unsigned int> v1{ 3U, 2U, 5U, 4U, 1U, 6U };
    
    // Perform a bitwise AND with all elements of v1; Results in
    //
    //    ( 3, 2, 1, 0, 1, 2 )
    //
    blaze::DynamicVector<int> v2( v1 & 3U );
    
    blaze::DynamicMatrix<unsigned int> M1{ { 3U, 2U, 5U },
                                           { 4U, 1U, 6U } };
    
    // Perform a bitwise AND with all elements of M1; Results in
    //
    //    ( 3, 2, 1 )
    //    ( 0, 1, 2 )
    //
    blaze::DynamicMatrix<unsigned int> M2( M1 & 3U );
    

    Bitwise OR

    Vector/Vector Bitwise OR

    Via the bitwise OR operator (i.e. operator|()) it is possible to perform an elementwise bitwise OR with dense vectors:

    blaze::DynamicVector<unsigned int> v1( 5UL ), v3;
    blaze::DynamicVector<unsigned short> v2( 5UL );
    
    // ... Initializing the vectors
    
    v3 = v1 | v2;  // Elementwise bitwise OR of two dense column vectors of different data type
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. Also note that it is only possible to use vectors with the same transpose flag:

    using blaze::columnVector;
    using blaze::rowVector;
    
    blaze::DynamicVector<unsigned int,columnVector> v1( 5UL );
    blaze::DynamicVector<unsigned int,rowVector>    v2( 5UL );
    
    v1 | v2;           // Compilation error: Cannot OR a column vector and a row vector
    v1 | trans( v2 );  // OK: Bitwise OR of two column vectors
    

    Furthermore, it is possible to use different element types in the two vector operands, but a bitwise OR of two vectors with the same element type is favorable due to possible vectorization of the operation:

    blaze::DynamicVector<unsigned int> v1( 100UL ), v2( 100UL ), v3;
    
    // ... Initialization of the vectors
    
    v3 = v1 | v2;  // Vectorized bitwise OR of an unsigned int vector
    

    Matrix/Matrix Bitwise OR

    The bitwise OR operator (i.e. operator|()) can also be used to perform an elementwise bitwise OR with dense matrices:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<unsigned int,columnMajor> M1( 7UL, 3UL );
    blaze::DynamicMatrix<unsigned short,rowMajor>  M2( 7UL, 3UL ), M3;
    
    // ... Initializing the matrices
    
    M3 = M1 | M2;  // Elementwise bitwise OR of two dense matrices of different data type
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. It is possible to use any combination of row-major and column-major matrices. Note however that in favor of performance using two matrices with the same storage order is favorable. The same argument holds for the element type: While it is possible to use matrices with different element type, using two matrices with the same element type potentially leads to better performance due to vectorization of the operation.

    blaze::DynamicMatrix<unsigned int> M1( 50UL, 70UL ), M2( 50UL, 70UL ), M3;
    
    // ... Initialization of the matrices
    
    M3 = M1 | M2;  // Vectorized bitwise OR of two row-major, unsigned int dense matrices
    

    Scalar Bitwise OR

    Is is also possible to perform a bitwise OR between a dense vector or dense matrix and a scalar value, which has the same effect as performing a bitwise OR by means of a uniform vector or matrix (see UniformVector and UniformMatrix). In Blaze it is possible to use all built-in/fundamental data types except bool as scalar values. Examples:

    blaze::DynamicVector<unsigned int> v1{ 3U, 2U, 5U, 4U, 1U, 6U };
    
    // Perform a bitwise OR with all elements of v1; Results in
    //
    //    ( 3, 3, 7, 7, 3, 3 )
    //
    blaze::DynamicVector<int> v2( v1 | 3U );
    
    blaze::DynamicMatrix<unsigned int> M1{ { 3U, 2U, 5U },
                                           { 4U, 1U, 6U } };
    
    // Perform a bitwise OR with all elements of M1; Results in
    //
    //    ( 3, 3, 7 )
    //    ( 7, 3, 3 )
    //
    blaze::DynamicMatrix<unsigned int> M2( M1 | 3U );
    

    Bitwise XOR

    Vector/Vector Bitwise XOR

    Via the bitwise XOR operator (i.e. operator^()) it is possible to perform an elementwise bitwise XOR with dense vectors:

    blaze::DynamicVector<unsigned int> v1( 5UL ), v3;
    blaze::DynamicVector<unsigned short> v2( 5UL );
    
    // ... Initializing the vectors
    
    v3 = v1 ^ v2;  // Elementwise bitwise XOR of two dense column vectors of different data type
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. Also note that it is only possible to use vectors with the same transpose flag:

    using blaze::columnVector;
    using blaze::rowVector;
    
    blaze::DynamicVector<unsigned int,columnVector> v1( 5UL );
    blaze::DynamicVector<unsigned int,rowVector>    v2( 5UL );
    
    v1 ^ v2;           // Compilation error: Cannot XOR a column vector and a row vector
    v1 ^ trans( v2 );  // OK: Bitwise XOR of two column vectors
    

    Furthermore, it is possible to use different element types in the two vector operands, but a bitwise XOR of two vectors with the same element type is favorable due to possible vectorization of the operation:

    blaze::DynamicVector<unsigned int> v1( 100UL ), v2( 100UL ), v3;
    
    // ... Initialization of the vectors
    
    v3 = v1 ^ v2;  // Vectorized bitwise XOR of an unsigned int vector
    

    Matrix/Matrix Bitwise XOR

    The bitwise XOR operator (i.e. operator^()) can also be used to perform an elementwise bitwise XOR with dense matrices:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<unsigned int,columnMajor> M1( 7UL, 3UL );
    blaze::DynamicMatrix<unsigned short,rowMajor>  M2( 7UL, 3UL ), M3;
    
    // ... Initializing the matrices
    
    M3 = M1 ^ M2;  // Elementwise bitwise XOR of two dense matrices of different data type
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. It is possible to use any combination of row-major and column-major matrices. Note however that in favor of performance using two matrices with the same storage order is favorable. The same argument holds for the element type: While it is possible to use matrices with different element type, using two matrices with the same element type potentially leads to better performance due to vectorization of the operation.

    blaze::DynamicMatrix<unsigned int> M1( 50UL, 70UL ), M2( 50UL, 70UL ), M3;
    
    // ... Initialization of the matrices
    
    M3 = M1 ^ M2;  // Vectorized bitwise XOR of two row-major, unsigned int dense matrices
    

    Scalar Bitwise XOR

    Is is also possible to perform a bitwise XOR between a dense vector or dense matrix and a scalar value, which has the same effect as performing a bitwise XOR by means of a uniform vector or matrix (see UniformVector and UniformMatrix). In Blaze it is possible to use all built-in/fundamental data types except bool as scalar values. Examples:

    blaze::DynamicVector<unsigned int> v1{ 3U, 2U, 5U, 4U, 1U, 6U };
    
    // Perform a bitwise XOR with all elements of v1; Results in
    //
    //    ( 0, 1, 6, 7, 2, 5 )
    //
    blaze::DynamicVector<int> v2( v1 ^ 3U );
    
    blaze::DynamicMatrix<unsigned int> M1{ { 3U, 2U, 5U },
                                           { 4U, 1U, 6U } };
    
    // Perform a bitwise XOR with all elements of M1; Results in
    //
    //    ( 0, 1, 6 )
    //    ( 7, 2, 5 )
    //
    blaze::DynamicMatrix<unsigned int> M2( M1 ^ 3U );
    

    Logical NOT

    Vector/Vector Logical NOT

    Via the logical NOT operator (i.e. operator!()) it is possible to compute an elementwise logical NOT of a dense vector:

    blaze::DynamicVector<bool> v1( 5UL ), v2;
    
    // ... Initializing the vectors
    
    v2 = !v1;  // Elementwise logical NOT of a dense column vector
    

    Matrix/Matrix Logical NOT

    The logical NOT operator (i.e. operator!()) can also be used to compute an elementwise logical NOT with dense matrices:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<bool,rowMajor> M1( 7UL, 3UL ), M2;
    
    // ... Initializing the matrices
    
    M2 = !M1;  // Elementwise logical NOT of a dense row-major matrix
    

    Logical AND

    Vector/Vector Logical AND

    Via the logical AND operator (i.e. operator&&()) it is possible to compute an elementwise logical AND with dense vectors:

    blaze::DynamicVector<bool> v1( 5UL ), v3;
    blaze::DynamicVector<bool> v2( 5UL );
    
    // ... Initializing the vectors
    
    v3 = v1 && v2;  // Elementwise logical AND of two dense column vectors
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. Also note that it is only possible to use vectors with the same transpose flag:

    using blaze::columnVector;
    using blaze::rowVector;
    
    blaze::DynamicVector<bool,columnVector> v1( 5UL );
    blaze::DynamicVector<bool,rowVector>    v2( 5UL );
    
    v1 && v2;           // Compilation error: Cannot AND a column vector and a row vector
    v1 && trans( v2 );  // OK: Logical AND of two column vectors
    

    Matrix/Matrix Logical AND

    The logical AND operator (i.e. operator&&()) can also be used to compute an elementwise logical AND with dense matrices:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<bool,columnMajor> M1( 7UL, 3UL );
    blaze::DynamicMatrix<bool,rowMajor>    M2( 7UL, 3UL ), M3;
    
    // ... Initializing the matrices
    
    M3 = M1 && M2;  // Elementwise logical AND of two dense matrices
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. It is possible to use any combination of row-major and column-major matrices. Note however that in favor of performance using two matrices with the same storage order is favorable.

    Logical OR

    Vector/Vector Logical OR

    Via the logical OR operator (i.e. operator||()) it is possible to perform an elementwise logical OR with dense vectors:

    blaze::DynamicVector<bool> v1( 5UL ), v3;
    blaze::DynamicVector<bool> v2( 5UL );
    
    // ... Initializing the vectors
    
    v3 = v1 || v2;  // Elementwise logical OR of two dense column vectors
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. Also note that it is only possible to use vectors with the same transpose flag:

    using blaze::columnVector;
    using blaze::rowVector;
    
    blaze::DynamicVector<unsigned int,columnVector> v1( 5UL );
    blaze::DynamicVector<unsigned int,rowVector>    v2( 5UL );
    
    v1 || v2;           // Compilation error: Cannot OR a column vector and a row vector
    v1 || trans( v2 );  // OK: Logical OR of two column vectors
    

    Matrix/Matrix Logical OR

    The logical OR operator (i.e. operator||()) can also be used to perform an elementwise logical OR with dense matrices:

    using blaze::rowMajor;
    using blaze::columnMajor;
    
    blaze::DynamicMatrix<bool,columnMajor> M1( 7UL, 3UL );
    blaze::DynamicMatrix<bool,rowMajor>    M2( 7UL, 3UL ), M3;
    
    // ... Initializing the matrices
    
    M3 = M1 || M2;  // Elementwise logical OR of two dense matrices
    

    Note that it is necessary that both operands have exactly the same dimensions. Violating this precondition results in an exception. It is possible to use any combination of row-major and column-major matrices. Note however that in favor of performance using two matrices with the same storage order is favorable.

  8. Matthias Moulin reporter

    Thank you Klaus for adding and documenting these features!

    Wrt the documentation: shouldn’t the shift and bit-wise scalar be restricted to an unsigned integral type to eliminate the use cases of a signed integral type and a floating point type?

  9. Matthias Moulin reporter

    I refactored the relational operator workarounds mentioned above using C++20’s concepts (something similar can be done using more verbose SFINAE in <= C++17) as this results in way less verbose (and more constraint; as it requires matching element types) template signatures and can be useful for someone else reading this far 🙂 . (Note that the noexcept is technically only correct for the non-dynamic dense vectors.)

    As the vector types are passed as-is instead of using the base type, avoiding the need of operator~ .

        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Equal(const VectorT& lhs, const VectorT& rhs) noexcept
        {
            return blaze::map(lhs, rhs, [](auto l, auto r) { return l == r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Equal(const VectorT& lhs,
                          typename VectorT::ElementType rhs) noexcept
        {
            return blaze::map(lhs, [rhs](auto l) { return l == rhs; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Equal(typename VectorT::ElementType lhs,
                          const VectorT& rhs) noexcept
        {
            return blaze::map(rhs, [lhs](auto r) { return lhs == r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto NonEqual(const VectorT& lhs, const VectorT& rhs) noexcept
        {
            return blaze::map(lhs, rhs, [](auto l, auto r) { return l != r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto NonEqual(const VectorT& lhs,
                             typename VectorT::ElementType rhs) noexcept
        {
            return blaze::map(lhs, [rhs](auto l) { return l != rhs; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto NonEqual(typename VectorT::ElementType lhs,
                             const VectorT& rhs) noexcept
        {
            return blaze::map(rhs, [lhs](auto r) { return lhs != r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Less(const VectorT& lhs, const VectorT& rhs) noexcept
        {
            return blaze::map(lhs, rhs, [](auto l, auto r) { return l < r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Less(const VectorT& lhs,
                         typename VectorT::ElementType rhs) noexcept
        {
            return blaze::map(lhs, [rhs](auto l) { return l < rhs; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Less(typename VectorT::ElementType lhs,
                         const VectorT& rhs) noexcept
        {
            return blaze::map(rhs, [lhs](auto r) { return lhs < r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto LessEqual(const VectorT& lhs, const VectorT& rhs) noexcept
        {
            return blaze::map(lhs, rhs, [](auto l, auto r) { return l <= r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto LessEqual(const VectorT& lhs,
                              typename VectorT::ElementType rhs) noexcept
        {
            return blaze::map(lhs, [rhs](auto l) { return l <= rhs; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto LessEqual(typename VectorT::ElementType lhs,
                              const VectorT& rhs) noexcept
        {
            return blaze::map(rhs, [lhs](auto r) { return lhs <= r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Greater(const VectorT& lhs, const VectorT& rhs) noexcept
        {
            return blaze::map(lhs, rhs, [](auto l, auto r) { return l > r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Greater(const VectorT& lhs,
                            typename VectorT::ElementType rhs) noexcept
        {
            return blaze::map(lhs, [rhs](auto l) { return l > rhs; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto Greater(typename VectorT::ElementType lhs,
                            const VectorT& rhs) noexcept
        {
            return blaze::map(rhs, [lhs](auto r) { return lhs > r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto GreaterEqual(const VectorT& lhs, const VectorT& rhs) noexcept
        {
            return blaze::map(lhs, rhs, [](auto l, auto r) { return l >= r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto GreaterEqual(const VectorT& lhs,
                                 typename VectorT::ElementType rhs) noexcept
        {
            return blaze::map(lhs, [rhs](auto l) { return l >= rhs; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline auto GreaterEqual(typename VectorT::ElementType lhs,
                                 const VectorT& rhs) noexcept
        {
            return blaze::map(rhs, [lhs](auto r) { return lhs >= r; });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline bool All(const VectorT& v) noexcept
        {
            return std::all_of(blaze::begin(v), blaze::end(v),
                               [](const auto& x) { return static_cast< bool >(x); });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline bool Any(const VectorT& v) noexcept
        {
            return std::any_of(blaze::begin(v), blaze::end(v),
                               [](const auto& x) { return static_cast< bool >(x); });
        }
    
        template< DenseVector VectorT >
        [[nodiscard]]
        inline bool None(const VectorT& v) noexcept
        {
            return std::none_of(blaze::begin(v), blaze::end(v),
                                [](const auto& x) { return static_cast< bool >(x); });
        }
    

  10. Klaus Iglberger

    Thanks for the sample code. Once Blaze is upgraded to C++17, this will definitely be useful.

  11. Log in to comment