I have been trying to create a class which represents a non-owning, multidimensional view of an array (sort of like an N-dimensional std::string_view
), where the dimensionality is varied "dynamically". Ie the number of dimensions and dimension sizes are not tied to the class, but specified when elements are accessed (through operator()
). The following code sums up the functionality I'm looking for:
#include <array>
#include <cstddef>
template<typename T>
struct array_view {
T* _data;
// Use of std::array here is not specific, I intend to use my own, but similar in functionality, indices class.
template<std::size_t N>
T& operator()(std::array<std::size_t, N> dimensions, std::array<std::size_t, N> indices) const
{
std::size_t offset = /* compute the simple offset */;
return _data[offset];
}
};
int main()
{
int arr[3 * 4 * 5] = {0};
array_view<int> view{arr};
/* Access element 0. */
// Should call array_view<int>::operator()<3>(std::array<std::size_t, 3>, std::array<std::size_t, 3>)
view({5, 4, 3}, {0, 0, 0}) = 1;
}
However this fails to compile (ignoring the obvious syntax error in operator()
) with
main.cpp: In function 'int main()':
main.cpp:28:27: error: no match for call to '(array_view<int>) (<brace-enclosed initializer list>, <brace-enclosed initializer list>)'
view({5, 4, 3}, {0, 0, 0}) = 1;
^
main.cpp:11:5: note: candidate: 'template<long unsigned int N> T& array_view<T>::operator()(std::array<long unsigned int, N>, std::array<long unsigned int, N>) const [with long unsigned int N = N; T = int]'
T& operator()(std::array<std::size_t, N> dimensions, std::array<std::size_t, N> indices) const
^~~~~~~~
main.cpp:11:5: note: template argument deduction/substitution failed:
main.cpp:28:27: note: couldn't deduce template parameter 'N'
view({5, 4, 3}, {0, 0, 0}) = 1;
^
I am not an expert on template instantiation/deduction. However it would seem to me that the compiler tries to deduce N
from std::initializer_list<int>
arguments, which fails because operator()
is declared to take std::array<std::size_t, N>
arguments. Hence compilation fails.
Doing another, far more simplified, experiment, shows similar results:
template<typename T>
struct foo {
T val;
};
struct bar {
template<typename T>
void operator()(foo<T>) {}
};
int main()
{
bar b;
b({1});
}
Output:
main.cpp: In function 'int main()':
main.cpp:14:7: error: no match for call to '(bar) (<brace-enclosed initializer list>)'
b({1});
^
main.cpp:8:10: note: candidate: 'template<class T> void bar::operator()(foo<T>)'
void operator()(foo<T>) {}
^~~~~~~~
main.cpp:8:10: note: template argument deduction/substitution failed:
main.cpp:14:7: note: couldn't deduce template parameter 'T'
b({1});
It seems the compiler does not even get to trying to convert {1}
(which is a valid initialisation of foo<int>
) to foo<int>
because it stops after failing to deduce the function template argument.
So is there any way to achieve the functionality I'm looking for? Is there some new syntax I'm missing, or an alternate approach that does the same, or is it simply not possible?