3

I am trying to make a function that returns zero filled array of array of array... Following Elegantly define multi-dimensional array in modern C++ I defined:

template<typename U, std::size_t N, std::size_t... M>                                                                                                                                                               
struct myTensor{                                                                                                                                                                                                    
  using type = std::array<typename myTensor<U, M...>::type, N>;                                                                                                                                                     
};                                                                                                                                                                                                                  
template<typename U, std::size_t N>                                                                                                                                                                                 
struct myTensor<U,N>{                                                                                                                                                                                               
  using type = std::array<U, N>;                                                                                                                                                                                    
};                                                                                                                                                                                                                  
template<typename U, std::size_t... N>                                                                                                                                                                              
using myTensor_t = typename myTensor<U, N...>::type;

Then I define the following template functions to fill with zeros:

template<typename U, std::size_t N, std::size_t... M>                                                                                                                                                               
myTensor_t<U, N, M...> Zero_Tensor(){                                                                                                                                                                               
  myTensor_t<U, N, M...> res;                                                                                                                                                                                       
  for(int i=0; i<N; i++)                                                                                                                                                                                            
    res[i] = Zero_Tensor<U, M...>();                                                                                                                                                                                
  return res;                                                                                                                                                                                                       
};                                                                                                                                                                                                                  
                                                                                                                                                                                                                    
template<typename U, std::size_t N>                                                                                                                                                                                 
myTensor_t<U, N> Zero_Tensor(){                                                                                                                                                                                     
  myTensor_t<U, N> res;                                                                                                                                                                                             
  for(int i=0; i<N; i++)                                                                                                                                                                                            
    res[i] = U(0);                                                                                                                                                                                                  
  return res;                                                                                                                                                                                                       
};

When I do for example

class myclass{
myTensor_t<int,3,3,5> x;
};

it compiles fine. If I try doing:

class myclass{
myTensor_t<int,3,3,5> x=Zero_Tensor<int,3,3,5>();
};

I get the following error at compile:

src/mytensor.hpp(107): error: no instance of overloaded function "Zero_Tensor" matches the argument list
      res[i] = Zero_Tensor<U, M...>();
               ^
src/mytensor.hpp(112): note: this candidate was rejected because function is not visible
  myTensor_t<U, N> Zero_Tensor(){
                   ^
src/mytensor.hpp(104): note: this candidate was rejected because at least one template argument could not be deduced
  myTensor_t<U, N, M...> Zero_Tensor(){
                         ^
          detected during:
            instantiation of "myTensor_t<U, N, M...> Zero_Tensor<U,N,M...>() [with U=int, N=5UL, M=<>]" at line 107
            instantiation of "myTensor_t<U, N, M...> Zero_Tensor<U,N,M...>() [with U=int, N=3UL, M=<5UL>]" at line 107
            instantiation of "myTensor_t<U, N, M...> Zero_Tensor<U,N,M...>() [with U=int, N=3UL, M=<3UL, 5UL>]" at line 36 of "src/myclass.hpp"

I don't really understand what this candidate was rejected because function is not visible is telling me. I guess I don't understand why it is not visible? Any help is appreciated.

Pana
  • 121
  • 7
  • res[i] = Zero_Tensor(); doesn't this line in zero_tensor create infinite loop. – Sudip Ghimire Jun 22 '20 at 19:22
  • Note that the definition of your `Zero_Tensor` function could be reduced to just one line: `return myTensor_t{};`. Value-initializing a `std::array` value-initializes every element of the array, which for numeric types like `int` will zero them. – Miles Budnek Jun 22 '20 at 19:25
  • @SudipGhimire I think the recursion is stopped by `myTensor_t Zero_Tensor()`. At least that is my understanding, maybe I am thinking about it completely incorrectly. – Pana Jun 22 '20 at 20:05
  • @MilesBudnek I see your point, very true. However what happens when I don't have primitive types? I am going to use this for some class type eventually that has a well defined `U(0)`. Further down the line rather than only zero initialize I will constant initialize with some well define `U(something)`. I never explained that in the question, so my apologies. – Pana Jun 22 '20 at 20:08

2 Answers2

2

Suggestion: rewrite your Zero_Tensor() functions as follows

template <typename U>
U Zero_Tensor () 
 { return U(0); }

template <typename U, std::size_t N, std::size_t... M>
myTensor_t<U, N, M...> Zero_Tensor ()
 {
   myTensor_t<U, N, M...> res;

   for ( auto i = 0u ; i < N ; ++i )
      res[i] = Zero_Tensor<U, M...>();

   return res;
 }

Now your problem is that the ground version (the end of the recursion, the <U, N> version) is defined after recursive version (the <U, N, M...>), so when the recursive version call

 Zero_Tensor<U, M...>();

and the M... pack is empty, the compiler doesn't know a Zero_Tensor() function that accept only a U type as template parameter.

You can invert the order of definitions (or declare the ground case before the recursive case) but you have another problem: when you call

 Zero_Tensor<U, M...>();

and the M... pack contain only a number, you have an ambiguous call because both version match.

Solution: use a simpler ground case

template <typename U>
U Zero_Tensor () 
 { return U(0); }

and define it before the recursive case.

This way the

 Zero_Tensor<U, M...>();

is never ambiguous because when the M... pack is empty, only the ground case matches, otherwise only the recursive case matches.

Off topic suggestion: when you have a cycle from zero to an unsigned number (as in your example)

// ............V  N is std::size_t, an unsigned type
for(int i=0; i<N; i++)

use an unsigned variable for the index variable (i) to avoid an annoying warning as "warning: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’"

max66
  • 65,235
  • 10
  • 71
  • 111
  • Excellent, thank you for walking me through the logic of why it doesn't work. One question comes to mind from this explanation, why did it work with the templated struct? There the base case being after the recursive case works fine. What am I missing? If you think my question is trivial could you please direct me to a reading source so I can figure that out? And your final comment/suggestion yes I agree it is good practice to mach our types properly, it can make it more transparent when something unexpectedly doesn't work to track down the issue. – Pana Jun 23 '20 at 00:07
  • @max66 is there a way i can wrap everything into a single class – Sudip Ghimire Jun 23 '20 at 06:42
  • 1
    @Pana - Unfortunately I'm not a language layer but... regarding the templated struct... it works because you have a single struct with a **specialization**, not two different (not directly related) functions. Is different and the compiler manage it differently. – max66 Jun 23 '20 at 10:31
  • @SudipGhimire - Sorry but I don't understand what you want to wrap. You want a single `myTensor` (without specialization) or you want that the `Zero_Tensor()` functions are integrated in the `myTensor` struct (and specialization)? – max66 Jun 23 '20 at 10:33
  • @max66 ok I see the point in that description. Again thank you, I think this clears is up for me. – Pana Jun 23 '20 at 14:32
2

The issue is that when you instantiate Zero_Tensor with M... being empty, the other overload of Zero_Tensor (with just 2 template parameters), is not visible. If you move that overload before the parameter pack version, you'll run into the issue that the overloads are ambiguous when passing in 2 template parameters.

You can resolve this by having just a single Zero_Tensor function, that decides what to do, based on the size of M..., like this:

template<typename U, std::size_t N, std::size_t... M>                                                                                                                                                               
myTensor_t<U, N, M...> Zero_Tensor(){                                                                                                                                                                               
  myTensor_t<U, N, M...> res;                                                                                                                                                                                       
  for(int i=0; i<N; i++)                                                                                                                                                                                            
    if constexpr(sizeof...(M) > 0)
      res[i] = Zero_Tensor<U, M...>();                                                                                                                                                                                
    else 
      res[i] = U(0);     
  return res;                                                                                                                                                                                                       
};                                                                                                                                                                                                                  

Here's a demo. Note that this requires c++17.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • Maybe I'm wrong, but reading your code, I think it's a pity there isn't a `constexpr` version of the ternary operator. – max66 Jun 23 '20 at 20:11
  • @max66 I assume you mean so that this works `res[i] = sizeof...(M) > 0 ? Zero_Tensor() : U(0);`? I see what you mean: a `constexpr if` version where the types could be different. That would be nice. – cigien Jun 23 '20 at 20:14