ChristopherC
ChristopherC

Reputation: 1665

Parsing template tuple parameters in D

I'm implementing a multi-dimensional tensor for a linear algebra library in D and this is basically what I was aiming to for the base class:

class Tensor( T_scalar, T_dimensions ..., int T_storageOrder = StorageOrder.columnMajor )
{
}

In the idea, the user can define the characteristics of the tensor through template parameters and as a result I can deduce as many things as possible during compile-time, a bit like Eigen does. Unfortunately the compiler is not so happy with that definition and triggers an error such as:

Tensor(T_scalar,T_args...,int T_storageOrder = StorageOrder.columnMajor) template tuple parameter must be last one

I'm not so sure why this restriction is in place but I ended up doing what I consider being a hack... basically, having defined the StorageOrder as an enum allows me to check if the last argument of the template tuple parameter matches one of the values from the enum, and if so I can use it to set the value of the StorageOrder for that tensor, otherwise I set it up with a default value.

enum StorageOrder : int
{
  columnMajor = -1,
  rowMajor = -2
}



class Tensor( T_scalar, T_args ... )
{
private:

  alias TensorTraits!( T_scalar, T_args ) traits;
  alias traits.dimensions T_dimensions;
  alias traits.storageOrder T_storageOrder;
}



struct TensorTraits( T_scalar, T_args ... )
    if ( areTemplateParametersValid!( T_scalar, T_args )() )
{
  static immutable auto dimensions = mixin( extractDataFromTemplateTupleParameter.dimensions );
  static immutable int storageOrder = extractDataFromTemplateTupleParameter.storageOrder;


private:

  static auto extractDataFromTemplateTupleParameter()
  {
    Tuple!( string, "dimensions", int, "storageOrder" ) templateTupleParameterData;
    static if ( T_args[$ - 1] == StorageOrder.columnMajor || T_args[$ - 1] == StorageOrder.rowMajor )
    {
      alias TypeTuple!( T_args[0 .. $ - 1] ) dimensionsTuple;
      templateTupleParameterData.storageOrder = T_args[$ - 1];
    }
    else
    {
      alias TypeTuple!( T_args ) dimensionsTuple;
      templateTupleParameterData.storageOrder = StorageOrder.columnMajor;
    }

    static assert( dimensionsTuple.length > 0,
        "No dimensions have been defined." );

    foreach ( dimension; dimensionsTuple )
    {
      static assert( isIntegral!( typeof( dimension ) ),
        "Dimensions sizes needs to be defined as integrals." );

      static assert( dimension >= 0,
        "Dimensions sizes cannot be negative." );
    }

    templateTupleParameterData.dimensions = dimensionsTuple.stringof;
    return templateTupleParameterData;
  }
}


static bool areTemplateParametersValid( T_scalar, T_args ... )()
{
  static assert( isNumeric!( T_scalar ),
      "The 'T_scalar' template argument is not a numeric type." );

  static assert( T_args.length > 0,
      "No dimensions have been defined." );

  return true;
}

Since I've just started with D, and since I'm not so sure about this hack, I would like to know if this sounds good to you guys or if there's maybe a better way of handling this?

Upvotes: 3

Views: 151

Answers (2)

ChristopherC
ChristopherC

Reputation: 1665

FYI this is what I ended up doing.

class Array( T_scalar, T_args ... )
{
private:

  alias ArrayTraits!( T_scalar, T_args ) traits;
  alias traits.isDynamic T_isDynamic;
  alias traits.shapeAtCompileTime T_shapeAtCompileTime;
  alias traits.sizeAtCompileTime T_sizeAtCompileTime;
  alias traits.storageOrder T_storageOrder;
  alias traits.dataType T_dataType;
}


struct ArrayTraits( T_scalar, T_args ... )
    if ( areTemplateParametersValid!( T_scalar, T_args )() )
{
private:

  static if ( hasFlag( Flags.storageOrder ) )
    alias T_args[0 .. $ - 1] shapeTuple;

  else
    alias T_args shapeTuple;


public:

  static immutable bool isDynamic = hasFlag( Flags.dynamic ) ? true : false;
  static immutable auto shapeAtCompileTime = getShapeAtCompileTime();
  static immutable size_t sizeAtCompileTime = getSizeAtCompileTime();
  static immutable StorageOrder storageOrder = hasFlag( Flags.storageOrder ) ?
      T_args[$ - 1] : defaultStorageOrder;

  static if ( hasFlag( Flags.dynamic ) == true )
    alias T_scalar[] dataType;

  else
    alias T_scalar[sizeAtCompileTime] dataType;


public:

  static auto getShapeAtCompileTime()
  {
    static if ( hasFlag( Flags.dynamic ) == true )
    {
      static assert( shapeTuple.length == 1,
          "The shape of a dynamic array needs to be defined at run-time." );

      size_t[1] shapeAtCompileTime = [Storage.dynamic];
      return shapeAtCompileTime;
    }
    else
    {
      static assert( shapeTuple.length > 0,
          "No dimensions have been defined." );

      size_t[shapeTuple.length] shapeAtCompileTime;
      foreach ( i, dimension; shapeTuple )
      {
        static assert( isIntegral!( typeof( dimension ) ),
            "Dimensions sizes for a static array needs to be defined as integrals." );

        static assert( dimension > 0,
            "Dimensions sizes for a static array cannot be null or negative." );

        shapeAtCompileTime[i] = dimension;
      }

      return shapeAtCompileTime;
    }
  }


  static size_t getSizeAtCompileTime()
  {
    if ( hasFlag( Flags.dynamic ) == true )
      return 0;

    size_t size = 1;
    foreach ( dimension; shapeAtCompileTime )
      size *= dimension;

    return size;
  }


private:

  /++ Parses the template tuple parameter to extract the different flags passed, if any. +/
  static int getFlags()
  {
    int flags = 0;
    if ( is( typeof( T_args[0] ) == Storage ) && T_args[0] == Storage.dynamic )
      flags |= Flags.dynamic;

    if ( is( typeof( T_args[$ - 1] ) == StorageOrder ) )
      flags |= Flags.storageOrder;

    return flags;
  }


  /++ Checks if the template tuple parameter contains a specific flag. +/
  static bool hasFlag( Flags flag )
  {
    return (getFlags() & flag) == 0 ? false : true;
  }


private:

  enum Flags : int
  {
    dynamic = 1 << 0,
    storageOrder = 1 << 1
  }
}


bool areTemplateParametersValid( T_scalar, T_args ... )()
{
  static assert( T_args.length > 0,
      "No dimensions have been defined." );

  return true;
}

Upvotes: 0

Peter Alexander
Peter Alexander

Reputation: 54270

Like you say, it's a hack, and you should avoid hacks where unnecessary.

One (obvious) solution is to move the storage order before the dimensions, although I'm guessing you want to use that default parameter.

To work around that, you could create have specific templates for row and column major:

// Generic Tensor with storage order before dimensions.
class Tensor( T_scalar, int T_storageOrder, T_dimensions... )
{
}

template TensorRowOrder( T_scalar, T_dimensions... )
{
    alias Tensor( T_scalar, StorageOrder.rowMajor, T_dimensions ) TensorRowOrder;
}

template TensorColumnOrder( T_scalar, T_dimensions... )
{
    alias Tensor( T_scalar, StorageOrder.columnMajor, T_dimensions ) TensorColumnOrder;
}

You can then use TensorRowOrder or TensorColumnOrder in user code, or just Tensor when you need the generic T_storageOrder.

Upvotes: 1

Related Questions