c++c++20template-meta-programmingnon-type-template-parameterctad

Is it possible to pass a braced-init-list as a template argument?


I have this class

template <typename ValueType, std::size_t Size>
struct ArrayPrimitive
{
  constexpr ArrayPrimitive(const ValueType (&array)[Size]) {
    std::copy(array, array + Size, data_);
  }
  ValueType data_[Size];
};

which is my attempt on generalizing the wrapper for passing string literals as NTTP. Then I declare this variable template, making use of CTAD

template <ArrayPrimitive array>
std::integral_constant<decltype(array), array> arr;

and afterwards use it in code as such

for (auto i : arr<{{2,4,6}}>.value.data_) std::cout << i << std::endl;

It compiles and properly prints all the values.

The compiler I used was the gcc13.2, I know it won't work on clang as of now due to c++20's support for class NTTP not being there yet. Also VS Code's code analyzer really doesn't like the brackets in template instantiation, which is a bit weird because it doesn't have problem with arr<"123">.

I want to know if it's standard compliant and won't break with future changes.

EDIT: Looking at the answer for the bug report GCC bug 111277, it seems GCC has indeed implemented CWG 2450, placing it in realms of intended behaviour, however only for this one compiler.

EDIT2: It seems it also compiles with MSVC if I write it like this https://gcc.godbolt.org/z/1sThrse3x


Solution

  • The code arr<{{2,4,6}}> is syntactically invalid. If you look at the syntax for a template-argument, then you will notice that this does not support expansion to a braced-init-list. You have to write arr<ArrayPrimitive{2, 4, 6}> instead, for now.

    The fact that GCC allows arr<{{2, 4, 6}}> is possibly related to a known GCC bug 57905. I have submitted a new GCC bug 111277. Clang rejects it.

    However, this looks to be a defect in the standard, as can be seen in CWG 2450. braced-init-list as a template-argument. This issue currently has status drafting, which means that informal consensus was reached, but no precise wording to resolve the issue is available yet.

    Further notes

    The variable template you're suggesting is very specific, and could be generalized to work for constants of all types. There's also no need to make a variable template with type std::integral_constant. std::integral_constant is only necessary when making a type alias template, for example.

    You could write:

    template <auto value>
    inline constexpr decltype(value) constant;
    

    Note: A std::constant type alias similar to this this has been suggested in a C++ proposal, but unsuccessfully.

    If you can't write arr<{2, 4, 6}> anyway, then you can just as well use the constant variable template above, like constant<std::array{2, 4, 6}>. This is syntactically valid because std::array{2, 5, 6} it is a valid postfix-expression, consisting of a simple-type-specifier and a braced-init-list.

    Alternative solutions

    If all you want is something like for (int x : arr<{{2,4,6}}>), you could also write

    The variable template has the advantage that it is constexpr and has static storage duration. However, this won't really matter for small arrays, like those with three elements.