Skip to content

Simplified user interface for tensorOps functions #227

@rrsettgast

Description

@rrsettgast

The tensor ops functions typically require a single/combination of integer arguments to set the bounds for the function. Take for example:

template< std::ptrdiff_t ISIZE, typename VECTOR >
LVARRAY_HOST_DEVICE CONSTEXPR_WITHOUT_BOUNDS_CHECK inline
auto l2NormSquared( VECTOR const & vector )
{
  static_assert( ISIZE > 0, "ISIZE must be greater than zero." );
  internal::checkSizes< ISIZE >( vector );

  auto norm = vector[ 0 ] * vector[ 0 ];
  for( std::ptrdiff_t i = 1; i < ISIZE; ++i )
  {
    norm = norm + vector[ i ] * vector[ i ];
  }
  return norm;
}

The call looks something like:

l2NormSquared<3>(array)

where the N(3) is always required, and the type VECTOR is deduced. This is a little bit clunky imo. We could do something where we deduced both a size and type. Like so:

#include <tuple>
#include <iostream>
#include <type_traits>

template< typename _T > 
  struct HasMemberFunction_size_impl 
  { 
private: 
    template< typename CLASS > static constexpr auto test( int )->decltype( std::is_convertible< decltype( std::declval< CLASS >().size(  ) ), int >::value, int() ) 
    { return CLASS::size(); } 
    template< typename CLASS > static constexpr auto test( ... )->int 
    { return sizeof(CLASS)/sizeof(std::declval<CLASS>()[0]);; } 
public: 
    static constexpr int value = test< _T >( 0 );
  }; 
template< typename CLASS > 
static constexpr int HasMemberFunction_size = HasMemberFunction_size_impl< CLASS >::value;




template< int N >
struct Tensor
{
    static constexpr int size() 
    { return N; }

    double & operator[]( int const i )
    {
        return data[i];
    }

    double const & operator[]( int const i ) const
    {
        return data[i];
    }

    double data[N];
};



template< typename T, int N>
double func_impl( T const & array )
{
    double rvalue = array[0]*array[0];
    for( int i=1 ; i<N ; ++i )
    {
        rvalue += array[i]*array[i];
    }
    return rvalue;
}

template< typename T, int N = HasMemberFunction_size<T> >
typename std::enable_if<!std::is_pointer<T>::value, double>::type func( T const & array )
{
   std::cout<<"Calling reference version"<<std::endl;
   return func_impl<T,N>(array);
}

template< int N, typename T >
typename std::enable_if<std::is_pointer<T>::value, double>::type func( T const array )
{
   std::cout<<"Calling pointer version"<<std::endl;
   return func_impl<T,N>(array);
}



////////////////////////////////////////////

int main()
{
    Tensor<3> t;

    t[0] = 1;
    t[1] = 2;
    t[2] = 3;


    double a[3] = {1,2,3};
    double * const pa = a;


    std::cout<<func(t)<<std::endl;
    std::cout<<func(a)<<std::endl;
    std::cout<<func<3>(pa)<<std::endl;

}

Here it is in a compiler explorer:
https://godbolt.org/z/K1P4T9qdE

If we were to standardize the way we return compile time sizes, this works fairly well. With a raw pointer you would still require the specification of N. With multi-dimensions this would be more complicated...but I don't think it is too prohibitive, and the usability is certainly nicer.

@corbett5 @klevzoff Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions