1

I am trying to write a class to represent a tensor Tensor and would like to provide the syntax tensor(i, j) for a 2 dimensional tensor, tensor (i, j, k) for a 3 dimensional tensor and so on.

What I want to know is if there is a c++ type safe way to declare such Tensor:operator()(int, int, ...) that accepts any number of int arguments (besides the C style with the macros va_start va_end) and how to use said arguments inside the function.

Thanks for your time.

max66
  • 65,235
  • 10
  • 71
  • 111
tigre200
  • 184
  • 1
  • 10
  • What about variadic template parameter packs? – user0042 Dec 16 '17 at 20:48
  • You can use C++ variadic templates and constraint the argument to be of a specific type (or convertible to a specific type). Using the resulting parameter packs is a bit of a pain, though. – Dietmar Kühl Dec 16 '17 at 20:49
  • Is the tensors dimension a run-time property, or a compile-time one? – StoryTeller - Unslander Monica Dec 16 '17 at 20:50
  • Sounds like you are reinventing `std::tuple`. If yours is not the same, at least you can steal implementation and design ideas from it. – Raymond Chen Dec 16 '17 at 20:52
  • @Raymond Chen tensor is essentially a vector or matrix or multidimensional array, not a set of different types. boost have MultiArray and Eigen library had tensor type. Hmm, if change of dimensions needed in run-time, this can be implemented with only variadic methods, in both case even tensor[i][j][k] form is possible (maybe a little easier with static dimensions) – Swift - Friday Pie Dec 17 '17 at 08:07

4 Answers4

2

Well, you could always use an ordinary parameter pack; but force a compilation failure unless all parameters are ints:

#include <utility>
#include <type_traits>


class foo {

public:

    template<typename ...Args,
         typename=std::void_t<std::enable_if_t
                      <std::is_same_v<Args, int>>...>>
    void operator()(Args ...args)
    {
    }
};

void bar()
{
    foo bar;

    bar(4, 2);
}

That will compile, but not this:

bar(4, "foo");

or

bar(4, 2.3);

Note that this will not compile either:

unsigned baz=2;

bar(4, baz);

If you need to accept unsigned values, tweat the template accordingly.

Note that the template does not need to use a forwarding reference, since the only acceptable parameters are plain ints. Within the template function, you now have a garden-variety parameter pack, that you would use the same way you'd use any other parameter pack.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
1

To accept also unsigned int and other types that are convertible to int, and if you can accept an upper limit (63, in the following example) to the number of integer argument, I propose to follow an example from W.F..

So you can develop a typer

template <typename T, std::size_t>
using typer = T;

and a recursive struct proOp

template <typename T, std::size_t N = 64U,
          typename = std::make_index_sequence<N>>
struct proOp;

template <typename T, std::size_t N, std::size_t... Is>
struct proOp<T, N, std::index_sequence<Is...>> : public proOp<T, N-1U>
 {
   using proOp<T, N-1U>::operator();

   void operator() (typer<T, Is>... ts)
    { }
 };

template <typename T>
struct proOp<T, 0U, std::index_sequence<>>
 {
   void operator() ()
    { }
 };

Inheriting from proOp<int>, Tensor become

struct Tensor : public proOp<int>
 {
   using proOp<int>::operator();
 };

The following is a full working example

#include <utility>

template <typename T, std::size_t>
using typer = T;

template <typename T, std::size_t N = 64U,
          typename = std::make_index_sequence<N>>
struct proOp;

template <typename T, std::size_t N, std::size_t... Is>
struct proOp<T, N, std::index_sequence<Is...>> : public proOp<T, N-1U>
 {
   using proOp<T, N-1U>::operator();

   void operator() (typer<T, Is>... ts)
    { }
 };

template <typename T>
struct proOp<T, 0U, std::index_sequence<>>
 {
   void operator() ()
    { }
 };

struct Tensor : public proOp<int>
 {
   using proOp<int>::operator();
 };

int main()
 {
   Tensor t;

   t(1, 2, 3);
   t(1, 2, 3, 4U); // accept also unsigned
   //t(1, "two"); // error
 }
max66
  • 65,235
  • 10
  • 71
  • 111
1

Another way could be make operator() recursive and use the first argument in every recursion

   // recursive case
   template <typename ... Ts>
   void operator() (int i0, Ts ... is)
    {
      // do something with i0
      this->operator()(is...); // recursion
    }

   void operator() ()
    { }

The following is a full working example

struct Tensor
 {
   // recursive case
   template <typename ... Ts>
   void operator() (int i0, Ts ... is)
    {
      // do something with i0
      this->operator()(is...); // recursion
    }

   void operator() ()
    { }
 };

int main()
 {
   Tensor t;

   t(1, 2, 3);
   t(1, 2, 3, 4U); // accept also unsigned
   //t(1, "two"); // error
 }
max66
  • 65,235
  • 10
  • 71
  • 111
0

There is a more laconic way for creating safe variadic function, without using recursion and std::enable_if:

template <typename ...Ints>
void function(int first, Ints... other)
{
    int values[] = {first, other...};
    for(int value : values)
    {
        //your code
    }
}

This way is also type safe and function(1, "2", 3) won't compile.

Yuriy
  • 701
  • 4
  • 18