I need to define a C++ template that accepts several 3D coordinates as their parameters. When all dimensions of these coordinates are defined as separate integer variables, the parameter list would become exceedingly long - 3 coordinates need 9 parameters, which makes the template hard to use.
Thus, it's highly desirable to declare the templates in a way to use compile-time arrays. Their default arguments should also be declared directly at the location of the template declaration as values, rather than as variable names.
After some experimentation, to my surprise, I found GCC 13 will accept the following C++ program with std=c++20
:
#include <cstdio>
#include <array>
template <
std::array<int, 3> offset = {0, 0, 0}
>
struct Array
{
void operator() (int i, int j, int k)
{
printf("(%d, %d, %d)\n", i + offset[0], j + offset[1], k + offset[2]);
}
};
int main(void)
{
Array arr_default;
arr_default(0, 0, 0);
Array<{1, 1, 1}> arr;
arr(0, 0, 0);
return 0;
}
However, clang 18 rejects the braced-init-list as invalid:
test2.cpp:5:30: error: expected expression
5 | std::array<int, 3> offset = {0, 0, 0}
| ^
test2.cpp:17:8: error: no viable constructor or deduction guide for deduction of template arguments of 'Array'
17 | Array arr_default;
| ^
test2.cpp:7:8: note: candidate template ignored: couldn't infer template argument 'offset'
7 | struct Array
| ^
test2.cpp:7:8: note: candidate function template not viable: requires 1 argument, but 0 were provided
7 | struct Array
| ^~~~~
test2.cpp:20:8: error: expected expression
20 | Array<{1, 1, 1}> arr;
| ^
3 errors generated.
Is it really a legal C++ program? If it is, what syntax should I use to convince clang to accept it? If it's not, how can I fix the code (and should I report a GCC bug for accepting it unquestionably)?
This is CWG 2450 and/or CWG 2049 and although the current grammar does not allow this, it is proposed to be allowed/valid for the reason mentioned below. That means, Gcc is just preemptively allowing the syntax. From CWG 2450:
Since non-type template parameters can now have class types, it would seem to make sense to allow a braced-init-list as a template-argument, but the grammar does not permit it.
(emphasis mine)
In case you're wondering how the current grammar makes does not allow this, from temp.name#1:
template-argument: constant-expression type-id id-expression
And since {1, 1, 1}
is not any of the above three listed constructs, it cannot be used as a template argument as per the current grammar rules.
Is there an alternative and more compatible way to achieve my goals?
You can explicitly write std::array
before the braced init list as shown below:
#include <cstdio>
#include <array>
template <
//------------------------------vvvvvvvvvv----------->added this
std::array<int, 3> offset = std::array{0, 0, 0}
>
struct Array
{
void operator() (int i, int j, int k)
{
printf("(%d, %d, %d)\n", i + offset[0], j + offset[1], k + offset[2]);
}
};
int main(void)
{
Array arr_default;
arr_default(0, 0, 0);
//--------vvvvvvvvvv------------------->added this
Array<std::array{1, 1, 1}> arr;
arr(0, 0, 0);
return 0;
}
Also note that clang trunk also starts accepting the program while gcc and msvc already accepted it from earlier versions.