2

Apologies if the title is misleading or if this question has been answered before.

I'm working with Eigen's Tensor module, particularly the Eigen::TensorFixedSize class as I know the shape at compile time.

Essentially, because this is a Lorentz problem, a rank-2 tensor would go like,

Eigen::TensorFixedSize<double, Eigen::Sizes<4,4>> t;

a rank-3 tensor,

Eigen::TensorFixedSize<double, Eigen::Sizes<4,4,4>> t;

and so on.

I'd like to write a class that is able to initialise a tensor depending on the rank. In pseudo-code,

template<typename RANK>
 class Foo
 {
  public:
   ...
  private:
   Eigen::TensorFixedSize<double, Eigen::Sizes<4,4,4,...,RANK times>> _t;
 }

somehow converting the template parameter from

<2> --> <4,4>

<3> --> <4,4,4>

up to an arbitrary unsigned int in <N>.

Would this be possible to do?

Quentin
  • 62,093
  • 7
  • 131
  • 191
jd13386
  • 23
  • 2

2 Answers2

3

Yup.

template <class RankIdx>
struct TensorForRank;

template <std::size_t... RankIdx>
struct TensorForRank<std::index_sequence<RankIdx...>> {
    using type = Eigen::TensorFixedSize<double, Eigen::Sizes<(void(RankIdx), 4)...>>;
};

template <std::size_t Rank>
using TensorForRank_t = typename TensorForRank<std::make_index_sequence<Rank>>::type;

Use as:

template<std::size_t Rank>
class Foo
{
    // ...
private:
    TensorForRank_t<Rank> _t;
};

See it live on Wandbox (with a placeholder test<...> template as Eigen is not available)

Quentin
  • 62,093
  • 7
  • 131
  • 191
1

Quentin's answer is very good, and what I'd go with.

The only downside is the "useless" generation of an index sequence [0, 1, 2, ...] whose values we ignore, and substitute for our own.

If we want to directly create the repeated values, we can write our own generator code (which is quite a bit more verbose):

Start with creating a type that can hold a number of std::size_t values by aliasing a std::integer_sequence:

template<std::size_t... vals>
using value_sequence = std::integer_sequence<std::size_t, vals...>;

The goal is to ultimately create a value_sequence<4, 4, 4> and then instantiate an Eigen::Sizes using those 4s.

The next thing we need to be able to do is concatenate two sequences, because we're going to build it up like so:

concat(value_sequence<4>, value_sequence<4>) --> value_sequence<4, 4>

We can do this via a stub method that accepts two value_sequence types and returns the concatenated result. Note that we do not ever write a definition for this method; we're simply taking advantage of the type system to write less code than a template specialization would take:

template<std::size_t... lhs, std::size_t... rhs>
constexpr auto concat(value_sequence<lhs...>, value_sequence<rhs...>) -> value_sequence<lhs..., rhs...>;

At this point we have enough machinery to create a value_sequence<4,4,4>, so now we need a way to indicate the value we wish to use (4) and the number of times to repeat it (3) to produce it:

template<std::size_t value, std::size_t num_repeats>
struct repeated_value
{
    using left_sequence = value_sequence<value>;
    using right_sequence = typename repeated_value<value, num_repeats-1>::type;
    using type = decltype(concat(left_sequence{}, right_sequence{}));
};

repeated_value<4, 3>::type produces a value_sequence<4, 4, 4>.

Since repeated_value<...>::type is recursive, we need to provide a base case via partial specialization:

template<std::size_t value>
struct repeated_value<value, 1>
{
    using type = value_sequence<value>;
};

Great. All that's left is for us to receive an Eigen::Sizes class and a value_sequence<4, 4, 4> type, and produce Eigen::Sizes<4, 4, 4>.

We can do this with partial template specialization again:

template<template<std::size_t...> class T, class...>
struct InstantiateWithRepeatedVals;

template<template<std::size_t...> class T, std::size_t... vals>
struct InstantiateWithRepeatedVals<T, value_sequence<vals...>> 
{
    using type = T<vals...>;
};

That it! Throw in a few helpers to make using it easier, and we're done:

template<std::size_t value, std::size_t num_repeats>
using repeated_value_t = typename repeated_value<value, num_repeats>::type;

template<template<std::size_t...> class T, std::size_t Value, std::size_t N>
using InstantiateWithRepeatedVals_t = typename InstantiateWithRepeatedVals<T, repeated_value_t<Value, N>>::type;

Now we can use it like so:

using my_type = InstantiateWithRepeatedVals_t<EigenSizes, 4, 3>;
static_assert(std::is_same_v<my_type, EigenSizes<4, 4, 4>>);

Live Demo

AndyG
  • 39,700
  • 8
  • 109
  • 143