Section 5 of the current draft has the following text:
For consistent template specialization resolution of explicit and implicit dimensions the std::extent of an array type declaration must match a template specialization with extent parameters.
template < typename A >
struct array_traits {
static constexpr unsigned rank = std::rank<A>::value ;
static constexpr size_t extent_0 = std::extent<A,0>::value ;
static constexpr size_t extent_1 = std::extent<A,1>::value ;
static constexpr size_t extent_2 = std::extent<A,2>::value ;
// etc.
};
template< size_t N0 , size_t N1 , size_t N2 >
struct array_traits< int[N0][N1][N2] > {
static constexpr size_t rank = 3 ;
static constexpr size_t extent_0 = N0 ;
static constexpr size_t extent_1 = N1 ;
static constexpr size_t extent_2 = N2 ;
};
array_traits< int[1][2][3] > // matches the partial specialization
array_traits< int[ ][ ][ ] > // matches the partial specialization
I'm interpreting this as meaning that the two instantiations at the end of the code snippet, array_traits<int[1][2][3]>
and array_traits<int[][][]>
, should both match the partial template specialization struct array_traits<int[N0][N1][N2]>
.
I believe there are two problems related to this.
Problem A
It is my assumption that if we want the above to work, then we would also want the same behavior for one-dimension arrays.
Unfortunately, the proposed change would NOT be backwards compatible with prior versions of the C++ standard; this would change how one-dimension arrays are matched in templates. The current behavior is as follows:
template <typename T>
struct A { static constexpr unsigned spec_picked = 0; };
template <typename T>
struct A<T[ ]> { static constexpr unsigned spec_picked = 1; };
template <typename T, unsigned N>
struct A<T[N]> { static constexpr unsigned spec_picked = 2; };
static_assert(0 == A<int >::spec_picked, "A<int> failed");
static_assert(1 == A<int[ ]>::spec_picked, "A<int[ ]> failed");
static_assert(2 == A<int[3]>::spec_picked, "A<int[3]> failed");
The proposed change would instead give us:
static_assert(0 == A<int >::spec_picked, "A<int> failed");
static_assert(2 == A<int[ ]>::spec_picked, "A<int[ ]> failed");
static_assert(2 == A<int[3]>::spec_picked, "A<int[3]> failed");
This would presumably be problematic, because it would prevent template metaprogrammers from distinguishing between array types with implicit dimensions and array types with explicit dimensions.
Problem B
If the proposed change was implemented as it is currently written, it is unclear to me what the values of the non-type template parameters would be when they happen to match an implicit dimension. Here's the test case I started writing when I began attempting an implementation in Clang:
template <typename T>
struct A
{
static constexpr bool specialized = false;
};
template <unsigned N0, unsigned N1, unsigned N2>
struct A<int[N0][N1][N2]>
{
static constexpr bool specialized = true;
static constexpr unsigned extent_0 = N0;
static constexpr unsigned extent_1 = N1;
static constexpr unsigned extent_2 = N2;
};
int main()
{
typedef A<int[1][2][3]> a0;
static_assert(a0::specialized == true);
static_assert(a0::extent_0 == 1);
static_assert(a0::extent_1 == 2);
static_assert(a0::extent_2 == 3);
typedef A<int[][][]> a1;
static_assert(a1::specialized == true);
static_assert(a1::extent_0 == /* ??? */);
static_assert(a1::extent_1 == /* ??? */);
static_assert(a1::extent_2 == /* ??? */);
}
Would the values of N0, N1 and N2 (and extent_0, extent_1 and extent_2) be 0 for the a1 type?
Summary
I am concerned that I am somehow misinterpreting the quoted section of the proposal. If I'm not, I think we need to change this, although it may make it more difficult to implement our proposal - because you'd need to have a large number of partial specializations to match all the possible permutations of implicit/explicit dimensions. E.g.
array_traits<T[ ][ ][ ]>
array_traits<T[N0][ ][ ]>
array_traits<T[ ][N0][ ]>
/* etc, you get the idea */