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

1

2

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

Chasidychasing answered 3/9, 2023 at 14:29 Comment(7)
You can change the constructor to accept std::array e.g. ArrayPrimitive(const std::array<ValueType, Size> array) and use it like arr<std::array{2,4,6}>, which works on all compilers.Stocktonontees
"won't work on clang as of now due to c++20's support for class NTTP not being there yet" Huh? It has had support for them for several years.Export
I don't think <{...}> is legal, GCC and MSVC reject it. If you go for <std::array{...}>, all three compilers accept it: gcc.godbolt.org/z/EYnxKaThhExport
@Export P0732R2 and P1907R1 are listed as partially supported, while P1814R0 is a flat no, so the full support still isn't thereChasidychasing
@Export this however seem to work, but for GCC only gcc.godbolt.org/z/8Md13vzra that's why I've wondered whether it's a compiler bug or intended behaviour not yet implemented by other compilersChasidychasing
Any reason not to use std::array?Clairvoyant
@Clairvoyant from a functional standpoint, not really, it's mainly just for syntactic sugar, I'm already encoding different types on type level with constructs like val<3.5> or str<"123"> and wanted to see how close I can get to it with arraysChasidychasing
C
5

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

  • for (int x : {2, 4, 6}) which relies on std::initializer_list
  • for (int x : arr<int>{2, 4, 6}) where arr is an alias template template <typename T> using arr = T[]
  • for (int x : std::array{2, 4, 6})

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.

Carrycarryall answered 3/9, 2023 at 16:12 Comment(4)
It’s a standard oversight; GCC is supposed to be correct here.Placid
@DavisHerring GCC rejects arr<{2, 4, 6}> though, it only accepts two levels of braces like arr<{{2, 4, 6}}> as in OP's code. The fact that it supports this (partially) looks mostly accidental to me. The GCC bug report also has not been closed, which further reinforces this idea.Carrycarryall
The mentioned compiler bug refers to braced lists used during template declaration for default parameter values, here however it's in context of template instantiation, so I believe it's a different case. And yea, I've put an integral_constant here mainly as an example, though the class I'm using in my code derives from it as it fulfils all my requirements, I use it for wrapping values of structural types known during compilation and passing them around as types for several different contexts, mainly operations involving boost::hana.Chasidychasing
@Chasidychasing you are right; it's not exactly the same as the bug you've mentioned, but possibly related. I have submitted a new bug gcc.gnu.org/bugzilla/show_bug.cgi?id=111277.Carrycarryall

© 2022 - 2024 — McMap. All rights reserved.