Function not visible durring instantiation of variadic templates

Viewed 156

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.

2 Answers

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’"

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.

Related