c++language-lawyerlist-initializationc++-templatesbraced-init-list

Can you use a braced-init-list as a (default) template argument?


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.

Question

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)?


Solution

  • Problem

    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?

    Solution

    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;
    }
    

    Note

    Also note that clang trunk also starts accepting the program while gcc and msvc already accepted it from earlier versions.