Allow Tagging Blaze Expressions

Issue #166 resolved
Nils Deppe created an issue

Hi Klaus,

I hope things are well with you!

I was wondering if it would be possible to add an extra type template parameter to all expressions that acts as a "name" for them. What I mean is that instead of blaze::Vector<VT, TF> there would be a blaze::Vector<VT, TF, Tag>. The reason for this is we have several different vectors that we use Blaze as the ET implementation for. However, they different vectors should not be mixed in expressions since they represent fundamentally different data. For example, we have a vector for the solution in physical space and a different vector for the solution in spectral space. We want to have the expressions carry this information as a template parameter so that they cannot accidentally be mixed together. This would be a huge win for us, but I'm not sure how easy/difficult it would be to implement. I realize it would be a fairly extensive change but is this something you would consider useful? We could help with the change, but would rather not do it if it's not something that would be welcome upstream. Let me know what your thoughts are on the matter :)

Cheers,

Nils

Comments (19)

  1. Klaus Iglberger

    Hi Nils!

    Thanks a lot for creating this proposal. We perfectly understand your intention and agree that this could indeed be a huge win for your application. At this point we also cannot think of a better way to provide the same feature, i.e. adding a tag to all vectors and matrices would probably be the best course of action (although we still think about alternatives).

    However, we can ascertain that this change would be indeed a huge effort. All vector and matrix classes (base classes as well as concrete classes) would have to be adapted since the tag would have to be passed via the concrete classes down to the base classes:

    struct SolutionSpaceTag {};
    blaze::DynamicVector<int,rowVector,SolutionSpaceTag> v;
    

    The change would also affect a large portion of the member functions of these classes and a huge portion of the operations on vectors and matrices. In summary, we estimate that this change would affect approx. 75% of the Blaze functionality. Additionally, we currently have no idea if this would harmonize with the changes we have planned for the next two releases that will move Blaze towards N-dimensional arithmetic (see issue #53). Therefore at this point in time we don't know if we could immediately act on this because we see some risk involved.

    In summary, we feel that this could indeed be a reasonable future change to Blaze. We will keep this issue on our radar and continue to evaluate the required effort and the risk involved. By all means, thanks for pointing out this potential improvement, this idea will definitely be considered in our next steps.

    Best regards,

    Klaus!

  2. Иннокентий Алайцев

    Hello!

    What about some kind of strong typedef? Is it possible to resolve this issue outside of Blaze by using strong typedefs for vectors/matrixes or even for their contents? Probably something like template<VT, TF>struct DomainStructure: public blaze::Vector<VT, TF>; could be a solution? Strong typedef implementation may also be useful.

    Best regards,

    Innokentiy

  3. Klaus Iglberger

    Hi Innokentiy!

    Thanks a lot for the suggestion. From our point of view, this would indeed be a reasonable alternate approach that we did not think about, but of course we cannot cannot speak for Nils' application. Thanks for joining the discussion,

    Best regards,

    Klaus!

  4. Nils Deppe reporter

    Hi,

    It's not clear to me how easy this solution is, though it looks like it could work. Looking at CustomVector it has a ResultType of DynamicVector. If this could be controlled I think that's all that is necessary. What I'm trying right now is:

    class DataVector
        : public blaze::DenseVector<DataVector, blaze::defaultTransposeFlag> {
     public:
      using This = DataVector;
      using BaseType = blaze::DenseVector<DataVector, blaze::defaultTransposeFlag>;
      using ResultType = DataVector;
      using ReturnType = const double&;
      using CompositeType = const DataVector&;
    ...
    };
    

    However, I don't know what I need to add to get this to work. The error I get is:

    /home/nils/Research/spack/opt/spack/linux-antergosrolling-x86_64/gcc-7.2.0/blaze-3.2-mnkglifq5s6ib5ltbvil4xt66fekqq2l/include/blaze/math/traits/AddTrait.h:151:66: error: function 'operator+<DataVector, DataVector, false>' with deduced return type cannot be used before it is defined
       struct AddType { using Type = decltype( std::declval<Type1>() + std::declval<Type2>() ); };
                                                                     ^
    /home/nils/Research/spack/opt/spack/linux-antergosrolling-x86_64/gcc-7.2.0/blaze-3.2-mnkglifq5s6ib5ltbvil4xt66fekqq2l/include/blaze/math/traits/AddTrait.h:158:26: note: in instantiation of member class 'blaze::AddTrait<DataVector, DataVector, void>::AddType' requested here
       using Type = typename If_< Or< IsConst<T1>, IsVolatile<T1>, IsReference<T1>
                             ^
    /home/nils/Research/spack/opt/spack/linux-antergosrolling-x86_64/gcc-7.2.0/blaze-3.2-mnkglifq5s6ib5ltbvil4xt66fekqq2l/include/blaze/math/traits/AddTrait.h:250:1: note: in instantiation of template class 'blaze::AddTrait<DataVector, DataVector, void>' requested here
    using AddTrait_ = typename AddTrait<T1,T2>::Type;
    ^
    /home/nils/Research/spack/opt/spack/linux-antergosrolling-x86_64/gcc-7.2.0/blaze-3.2-mnkglifq5s6ib5ltbvil4xt66fekqq2l/include/blaze/math/expressions/DVecDVecAddExpr.h:164:26: note: in instantiation of template type alias 'AddTrait_' requested here
       using ResultType    = AddTrait_<RE1,RE2>;           //!< Result type for expression template evaluations.
                             ^
    /home/nils/Research/spack/opt/spack/linux-antergosrolling-x86_64/gcc-7.2.0/blaze-3.2-mnkglifq5s6ib5ltbvil4xt66fekqq2l/include/blaze/math/expressions/DVecDVecAddExpr.h:1067:11: note: in instantiation of template class 'blaze::DVecDVecAddExpr<DataVector, DataVector, false>' requested here
       return ReturnType( ~lhs, ~rhs );
              ^
    ../tests/Unit/DataStructures/Test_DataVector.cpp:201:12: note: in instantiation of function template specialization 'blaze::operator+<DataVector, DataVector, false>' requested here
        b = m0 + m1;
               ^
    /home/nils/Research/spack/opt/spack/linux-antergosrolling-x86_64/gcc-7.2.0/blaze-3.2-mnkglifq5s6ib5ltbvil4xt66fekqq2l/include/blaze/math/expressions/DVecDVecAddExpr.h:1058:4: note: 'operator+<DataVector, DataVector, false>' declared here
       operator+( const DenseVector<VT1,TF>& lhs, const DenseVector<VT2,TF>& rhs )
    

    Any ideas? I'm not defining an operator+(DataVector, DataVector) since I'd like to use the one for DenseVector.

    Thanks!

    Cheers,

    Nils

  5. Klaus Iglberger

    Hi Nils!

    Deriving directly from blaze::DenseVector would require you to implement a completely new vector type. This would indeed be a lot of work (also since we have admittedly not yet provided a tutorial about how to do this properly).

    A possible solution that works would be the following one. I have implemented the two dense vectors DataVector and SolutionVector that are both based on CustomVector and that should not be combined via operator+. I have added the capability to automatically clean up the allocated storage as this might be of interest for you.

    class DataVector
       : public CustomVector< double, unaligned, unpadded, defaultTransposeFlag >
    {
     public:
       using Base = CustomVector< double, unaligned, unpadded, defaultTransposeFlag >;
    
       explicit inline DataVector( size_t n )
          : CustomVector<double,unaligned,unpadded,defaultTransposeFlag>()
          , array_( new double[n] )
       {
          this->reset( array_.get(), n );
       }
    
       using Base::operator=;
    
     private:
       std::unique_ptr<double[]> array_;
    };
    
    class SolutionVector
       : public CustomVector< double, unaligned, unpadded, defaultTransposeFlag >
    {
     public:
       using Base = CustomVector< double, unaligned, unpadded, defaultTransposeFlag >;
    
       explicit inline SolutionVector( size_t n )
          : CustomVector<double,unaligned,unpadded,defaultTransposeFlag>()
          , array_( new double[n] )
       {
          this->reset( array_.get(), n );
       }
    
       using Base::operator=;
    
     private:
       std::unique_ptr<double[]> array_;
    };
    
    decltype(auto) operator+( DataVector const& a, DataVector const& b )
    {
       using Base = DataVector::Base;
       return static_cast<Base const&>( a ) + static_cast<Base const&>( b );
    }
    
    decltype(auto) operator+( SolutionVector const& a, SolutionVector const& b )
    {
       using Base = SolutionVector::Base;
       return static_cast<Base const&>( a ) + static_cast<Base const&>( b );
    }
    
    void operator+( DataVector const& a, SolutionVector const& b );
    void operator+( SolutionVector const& a, DataVector const& b );
    

    As you can see it is necessary to define some addition operators. Else the built-in operator+ would be used, which just works with blaze::DenseVector and that could not distinguish between DataVector and SolutionVector. However, the implementation of these custom operator+ is based on the Blaze implementation (i.e. only a little overhead) and clearly spells out which combinations are incorrect. Therefore it might be an acceptable solution.

    I don't know if this meets your requirements, but I hope it helps.

    Best regards,

    Klaus!

  6. Nils Deppe reporter

    Hi Klaus,

    Thanks for showing the implementation. Unfortunately this has the exact problem I'm trying to prevent:

    blaze::DataVector ten_dv(5);
    blaze::DataVector two_dv(5);
    const auto result_dv = ten_dv + two_dv;
    blaze::SolutionVector ten_sv(5);
    blaze::SolutionVector two_sv(5);
    const auto result_combined = (ten_dv + two_dv) + (ten_sv + two_sv);
    

    The last line compiles when I don't want it to. Any ideas on how to deal with that? I still think being able to control ResultType would be sufficient for my needs. If I do TD<typename decltype(result_combined)::ResultType> aoeu{}; to get the resulting type it tells me blaze::DynamicVector. I think if the ResultType of SolutionVector was a SolutionVector not a DynamicVector then everything would be check the way I want. This probably requires no changes in Blaze (other than documenting this secret feature if it works :) ). It's a bit more work on my part, but I'm okay with that if it means I get the type tracking.

    Thank you for the help! :)

    Cheers,

    Nils

  7. Klaus Iglberger

    Hi Nils!

    Your are correct, for more complex expressions this simple solution will fail. Thus I have a second proposal, the TaggedVector adaptor (which might be similar to what you have in mind):

    template< typename VT, typename Tag >
    class TaggedVector : public DenseVector< TaggedVector<VT,Tag>, TransposeFlag_v<VT> >
    {
     public:
       using This          = TaggedVector<VT,Tag>;
       using BaseType      = DenseVector<This,TransposeFlag_v<VT>>;
       using ResultType    = TaggedVector<ResultType_t<VT>,Tag>;
       using TransposeType = TaggedVector<TransposeType_t<VT>,Tag>;
    
       using ElementType   = ElementType_t<VT>;
       using SIMDType      = SIMDTrait_t<ElementType>;
       using ReturnType    = const ElementType&;
       using CompositeType = const TaggedVector&;
    
       using Reference      = ElementType&;
       using ConstReference = const ElementType&;
       using Pointer        = ElementType*;
       using ConstPointer   = const ElementType*;
    
       using Iterator      = Iterator_t<VT>;
       using ConstIterator = ConstIterator_t<VT>;
    
       static constexpr bool simdEnabled = VT::simdEnabled;
       static constexpr bool smpAssignable = VT::smpAssignable;
    
       explicit TaggedVector()
          : vec_()
       {}
    
       template< typename... Args, typename = EnableIf_t< And_v< Not< IsVector<Args> >... > > >
       explicit TaggedVector( Args&&... args )
          : vec_( std::forward<Args>( args )... )
       {}
    
       template< typename VT2, bool TF >
       explicit TaggedVector( const Vector<VT2,TF>& vec )
          : vec_( vec )
       {
          static_assert( IsSame_v<ResultType_t<VT2>,This>, "Invalid result type detected" );
       }
    
       Reference operator[]( size_t index ) { return vec_[index]; }
       ConstReference operator[]( size_t index ) const { return vec_[index]; }
    
       size_t size() const { return vec_.size(); }
       size_t capacity() const { return vec_.capacity(); }
    
       Iterator begin() { return vec_.begin(); }
       ConstIterator begin() const { return vec_.begin(); }
       Iterator end() { return vec_.begin(); }
       ConstIterator end() const { return vec_.end(); }
    
       TaggedVector& operator=( const TaggedVector& vec )
       {
          vec_ = vec.vec_;
          return *this;
       }
    
       template< typename VT2, bool TF >
       TaggedVector& operator=( const Vector<VT2,TF>& vec )
       {
          static_assert( IsSame_v<ResultType_t<VT2>,This>, "Invalid result type detected" );
          vec_ = vec;
          return *this;
       }
    
       template< typename Other > inline bool canAlias ( const Other* alias ) const noexcept { return vec_.canAlias( alias ); }
       template< typename Other > inline bool isAliased( const Other* alias ) const noexcept { return vec_.isAliased( alias ); }
    
     private:
       VT vec_;
    };
    
    template< typename VT1, typename Tag, typename VT2 >
    struct AddTrait< TaggedVector<VT1,Tag>, TaggedVector<VT2,Tag> >
    {
       using Type = TaggedVector< AddTrait_t<VT1,VT2>, Tag >;
    };
    
    template< typename VT1, typename Tag1, typename VT2, typename Tag2 >
    struct AddTrait< TaggedVector<VT1,Tag1>, TaggedVector<VT2,Tag2> >
    {};
    
    template< typename VT1, typename Tag, typename VT2, bool TF >
    struct AddTrait< TaggedVector<VT1,Tag>, DynamicVector<VT2,TF> >
    {};
    
    template< typename VT1, bool TF, typename VT2, typename Tag >
    struct AddTrait< DynamicVector<VT1,TF>, TaggedVector<VT2,Tag> >
    {};
    

    The class is incomplete, but demonstrates the intent: TaggedVector wraps another vector type (DynamicVector, CustomVector, ...) and adds a tag type. The result type is always a TaggedVector, which can be exploited in the operation traits (I have only added a few AddTrait specializations in this short demo). Whenever a TaggedVector is used in combination with another vector type, there will be a compilation error (AddTrait cannot determine the resulting type). Also, when TaggedVector is used in combination with another TaggedVector with different tag type, there will be a compilation error. An operation is only allowed if a TaggedVector is combined with a TaggedVector with the same tag type.

    Once again, the class is incomplete and a lot of details need to be properly implemented, but it hopefully serves the purpose to demonstrate the idea.

    Best regards,

    Klaus!

  8. Nils Deppe reporter

    Hi Klaus,

    I'm currently exploring what you've suggested. We have our own implementation of CustomVector so I'm adding the tagging ability to that. I'll give an update once I have things understood/working. Thank you for the help!

    Cheers,

    Nils

  9. Nils Deppe reporter

    Hi Klaus,

    Sorry this took so long to figure out, but I have it working now. All that's necessary (with some oversimplification) is adding a template parameter typename ExprResultType = blaze::DynamicVector<blaze::RemoveConst_<Type>, TF> to CustomVector, and a similar one for CustomMatrix. See this commit for how I do it with our PointerVector class, which at this point might be exactly the same as CustomVector (now with the new ExprResultType). I think this would be super easy to integrate into Blaze and would be exactly what I'm looking for :) I'm happy to do this in a PR, unless you want to implement it yourself.

    Cheers,

    Nils

  10. Klaus Iglberger

    Hi Nils!

    I'm sorry for the late reply, but I was thinking about this idea for the past two days. I'm still struggling to decide whether this as a special solution for your case (since "tagging" only works when you use CustomVector or CustomMatrix) or a general solution for all Blaze users (since you can manually define the resulting vector or matrix type). I'm also not thrilled by the idea to introduce a second defaulted template parameter (there should be only one at maximum), but so far I couldn't think of a better approach. I will continue to consider the idea and your requirements.

    Best regards,

    Klaus!

  11. Nils Deppe reporter

    Hi Klaus,

    Thanks for the update on what you're thinking! I'm just curious what your reasons are against a second defaulted template parameter? In this particular case it would be rarely used since normally there would be a class deriving off CustomVector that would also be the ResultType. Thanks for the consideration!

    Best,

    Nils

  12. Klaus Iglberger

    Hi Nils!

    I'm starting to believe that your solution can be of general interest for a greater audience. Providing the option to specify the result type explicitly allows for other kinds of optimization that have not been possible so far (e.g. you could specify StaticVector instead of DynamicVector in case you know that your CustomVector has a specific size; tagging would just be a special use for this). However, users should be unaware of the implementation details of CustomVector. In your commit it is necessary to use RemoveConst_t in the interface:

    template <typename Type, bool AF = blaze::unaligned, bool PF = blaze::unpadded,
              bool TF = blaze::defaultTransposeFlag,
              typename ExprResultType =
                  blaze::DynamicVector<blaze::RemoveConst_<Type>, TF>>
    struct PointerVector { /*...*/ };
    

    If you agree to this compromise then I believe the approach is viable:

    template< typename Type                     // Data type of the vector
            , bool AF                           // Alignment flag
            , bool PF                           // Padding flag
            , bool TF = defaultTransposeFlag    // Transpose flag
            , typename ExprResultType = DynamicVector<Type,TF> >  // <- Can be any general vector type
    class CustomVector
       : public DenseVector< CustomVector<Type,AF,PF,TF,ExprResultType>, TF >
    {
       // ...
       //! Result type for expression template evaluations.
       using ResultType = typename ExprResultType::BLAZE_TEMPLATE Rebind< RemoveConst_t<Type> >::Other;
    
       //! Transpose type for expression template evaluations.
       using TransposeType = TransposeType_t<ResultType>;
       // ...
    };
    

    Best regards,

    Klaus!

  13. Nils Deppe reporter

    Hi Klaus!

    That looks great and it would be awesome to have in Blaze! :) Thank you so much, we all really appreciate it :)

    Best,

    Nils

  14. Klaus Iglberger

    Commit 508e966 updates the CustomVector class requested. The modification is slightly different from the proposed change, but guarantees that even the nested Rebind and Resize class templates work correctly. Commit d41a167 updates the CustomMatrix class accordingly.

    Since the updates of CustomVector and CustomMatrix do not generally solve the tagging problem, we leave the issue open until a general solution has been found and implemented.

  15. Klaus Iglberger

    Intermediate Summary

    The last push introduced the ability to tag vectors and matrices and by that associate both with predefined or custom groups. Since the implementation details (e.g. the final order of template parameters) depend on the modifications done in issue 307, these two issues will be resolved together.

    The following gives an overview of the current (potentially not final) implementation.


    Tagging and Groups

    Sometimes it may be desirable to separate two or more distinct groups of vectors and matrices, for instance in order to allow operations only within a group and to prevent operations across groups. This goal can be achieved by means of tags. All vector and matrix classes provide a template parameter to specify a tag (for instance, the third template parameter for blaze::DynamicVector and the sixth template parameter for blaze::StaticVector):

    template< typename Type, bool TF, typename Tag >
    class DynamicVector;
    
    template< typename Type, size_t N, bool TF, AlignmentFlag AF, PaddingFlag PF, typename Tag >
    class StaticVector;
    

    By default, all vectors and matrices are associated with blaze::Group0 (i.e. the tag is set to blaze::Group0). However, it is possible to explicitly associate vectors and matrices with different groups:

    using blaze::DynamicVector;
    using blaze::Group0;
    using blaze::Group1;
    using blaze::columnVector;
    
    DynamicVector<int,columnVector,Group0> a0, b0;
    DynamicVector<int,columnVector,Group1> a1, b1;
    
    a0 + b0;  // Compiles, a0 and b0 are in the same group (Group0)
    a1 + b1;  // Compiles, a1 and b1 are in the same group (Group1)
    a0 + b1;  // Compilation error: a0 and b1 are not in the same group
    

    All vectors or matrices that are associated with the same group can be freely combined with any other vector or matrix from the same group. The attempt to combine vectors and matrices from different groups results in a compilation error.


    Creating New Groups

    Blaze provides the tags for the ten predefined groups blaze::Group0 through blaze::Group9. In order to create further groups, all that needs to be done is to create new instances of the blaze::GroupTag class template:

    using Group10 = blaze::GroupTag<10>;
    using Group11 = blaze::GroupTag<11>;
    // ... further groups
    

    All groups based on the blaze::GroupTag class template will be treated as separate groups just as the ten predefined groups.


    Custom Tags

    Sometimes it is not enough to separate vectors and matrices into different groups, but it is required to define the interaction between different groups. This situation for instance occurs if a vector or matrix is associated with a physical quantity. This problem can be solved by using custom tags. The following example gives an impression on how to define the physics on meters (represented by the Meter tag) and seconds (represented by the Second tag):

    struct Meter {};           // Definition of the 'Meter' tag
    struct Second {};          // Definition of the 'Second' tag
    struct SquareMeter {};     // Definition of the 'SquareMeter' tag
    struct MeterPerSecond {};  // Definition of the 'MeterPerSecond' tag
    

    The Meter and Second tags are not associated with the blaze::GroupTag class template. For that reason, by default, it is not possible to perform any operation on an accordingly tagged vector or matrix. All required operations need to be declared explicitly in order to specify the resulting tag of an operation. In the following code example, this happens by declaring both the addition for the Meter tag and the Second tag, the multiplication between two Meter tags and the division between Meter and Second. Note that it is enough to declare the operations, it is not necessary to define them!

    Meter          operator+( Meter , Meter  );  // Enabling addition between 'Meter'
    Second         operator+( Second, Second );  // Enabling addition between 'Second'
    SquareMeter    operator*( Meter , Meter  );  // Enabling multiplication between 'Meter'
    MeterPerSecond operator/( Meter , Second );  // Enabling division between 'Meter' and 'Second'
    

    With these declarations it is now possible to add meters and seconds, but not to subtract them (no subtraction operator was declared). Also, it is possible to multiply meters and to divide meters and seconds:

    const DynamicVector<int,rowVector,Meter> m1{ 1, 2, 3 };
    const DynamicVector<int,rowVector,Meter> m2{ 4, 5, 6 };
    
    const DynamicVector<int,rowVector,Second> s1{ 1, 2, 3 };
    const DynamicVector<int,rowVector,Second> s2{ 4, 5, 6 };
    
    m1 + m2;  // Compiles and results in vector tagged with 'Meter'
    s1 + s2;  // Compiles and results in vector tagged with 'Second'
    
    m1 - m2;  // Compilation error: No subtraction defined for 'Meter'!
    m1 + s2;  // Compilation error: No addition between 'Meter' and 'Second' defined!
    
    m1 * m2;  // Compiles and results in vector tagged with 'SquareMeter'
    m1 / s1;  // Compiles and results in vector tagged with 'MeterPerSecond'
    

    At this point it is possible to use the {{{pow2()}}} function for vectors and matrices tagged with {{{Meter}}} since {{{pow2()}}} is based on multiplication, which has already been declared. However, it is not possible to use the {{{abs()}}} function:

    pow2( m1 );  // Compiles and results in vector tagged with 'SquareMeter'
    abs ( m1 );  // Compilation error: No 'abs()' declared for the 'Meter' tag
    

    In order to enable the abs() function it also needs to be explicitly declared for the Meter tag:

    Meter abs( Meter );  // Enabling the 'abs()' function on 'Meter'
    
    abs( m1 );  // Compiles and results in vector tagged with 'Meter'
    

  16. Klaus Iglberger

    Summary

    With the recent additions to Blaze it is now possible to group/tag vectors and matrices. The feature is immediately available via cloning the Blaze repository and will be officially released in Blaze 3.8.


    Tagging and Groups

    Sometimes it may be desirable to separate two or more distinct groups of vectors and matrices, for instance in order to allow operations only within a group and to prevent operations across groups. This goal can be achieved by means of tags. All vector and matrix classes provide a template parameter to specify a tag (for instance, the fourth template parameter for blaze::DynamicVector and the sixth template parameter for blaze::StaticVector):

    template< typename Type, bool TF, typename Alloc, typename Tag >
    class DynamicVector;
    
    template< typename Type, size_t N, bool TF, AlignmentFlag AF, PaddingFlag PF, typename Tag >
    class StaticVector;
    

    By default, all vectors and matrices are associated with blaze::Group0 (i.e. the tag is set to blaze::Group0). However, it is possible to explicitly associate vectors and matrices with different groups:

    using blaze::DynamicVector;
    using blaze::AlignedAllocator;
    using blaze::Group0;
    using blaze::Group1;
    using blaze::columnVector;
    
    DynamicVector<int,columnVector,AlignedAllocator<int>,Group0> a0, b0;
    DynamicVector<int,columnVector,AlignedAllocator<int>,Group1> a1, b1;
    
    a0 + b0;  // Compiles, a0 and b0 are in the same group (Group0)
    a1 + b1;  // Compiles, a1 and b1 are in the same group (Group1)
    a0 + b1;  // Compilation error: a0 and b1 are not in the same group
    

    All vectors or matrices that are associated with the same group can be freely combined with any other vector or matrix from the same group. The attempt to combine vectors and matrices from different groups results in a compilation error.


    Creating New Groups

    Blaze provides the tags for the ten predefined groups blaze::Group0 through blaze::Group9. In order to create further groups, all that needs to be done is to create new instances of the blaze::GroupTag class template:

    using Group10 = blaze::GroupTag<10>;
    using Group11 = blaze::GroupTag<11>;
    // ... further groups
    

    All groups based on the blaze::GroupTag class template will be treated as separate groups just as the ten predefined groups.


    Custom Tags

    Sometimes it is not enough to separate vectors and matrices into different groups, but it is required to define the interaction between different groups. This situation for instance occurs if a vector or matrix is associated with a physical quantity. This problem can be solved by using custom tags. The following example gives an impression on how to define the physics on meters (represented by the Meter tag) and seconds (represented by the Second tag):

    struct Meter {};           // Definition of the 'Meter' tag
    struct Second {};          // Definition of the 'Second' tag
    struct SquareMeter {};     // Definition of the 'SquareMeter' tag
    struct MeterPerSecond {};  // Definition of the 'MeterPerSecond' tag
    

    The Meter and Second tags are not associated with the blaze::GroupTag class template. For that reason, by default, it is not possible to perform any operation on an accordingly tagged vector or matrix. All required operations need to be declared explicitly in order to specify the resulting tag of an operation. In the following code example, this happens by declaring both the addition for the Meter tag and the Second tag, the multiplication between two Meter tags and the division between Meter and Second. Note that it is enough to declare the operations, it is not necessary to define them!

    Meter          operator+( Meter , Meter  );  // Enabling addition between 'Meter'
    Second         operator+( Second, Second );  // Enabling addition between 'Second'
    SquareMeter    operator*( Meter , Meter  );  // Enabling multiplication between 'Meter'
    MeterPerSecond operator/( Meter , Second );  // Enabling division between 'Meter' and 'Second'
    

    With these declarations it is now possible to add meters and seconds, but not to subtract them (no subtraction operator was declared). Also, it is possible to multiply meters and to divide meters and seconds:

    const DynamicVector<int,rowVector,AlignedAllocator<int>,Meter> m1{ 1, 2, 3 };
    const DynamicVector<int,rowVector,AlignedAllocator<int>,Meter> m2{ 4, 5, 6 };
    
    const DynamicVector<int,rowVector,AlignedAllocator<int>,Second> s1{ 1, 2, 3 };
    const DynamicVector<int,rowVector,AlignedAllocator<int>,Second> s2{ 4, 5, 6 };
    
    m1 + m2;  // Compiles and results in vector tagged with 'Meter'
    s1 + s2;  // Compiles and results in vector tagged with 'Second'
    
    m1 - m2;  // Compilation error: No subtraction defined for 'Meter'!
    m1 + s2;  // Compilation error: No addition between 'Meter' and 'Second' defined!
    
    m1 * m2;  // Compiles and results in vector tagged with 'SquareMeter'
    m1 / s1;  // Compiles and results in vector tagged with 'MeterPerSecond'
    

    At this point it is possible to use the pow2() function for vectors and matrices tagged with Meter since pow2() is based on multiplication, which has already been declared. However, it is not possible to use the abs() function:

    pow2( m1 );  // Compiles and results in vector tagged with 'SquareMeter'
    abs ( m1 );  // Compilation error: No 'abs()' declared for the 'Meter' tag
    

    In order to enable the abs() function it also needs to be explicitly declared for the Meter tag:

    Meter abs( Meter );  // Enabling the 'abs()' function on 'Meter'
    
    abs ( m1 );  // Compiles and results in vector tagged with 'Meter'
    
  17. Log in to comment