13

Coming up from another question:

Since C++17, auto x0{1, 2, 3, 4};, previously deducing an initialiser list, is not allowed any more (sure, we can use auto x0 = {1, 2, 3, 4}; instead...). Now as always avoiding uniform initialisation (e. g. std::vector<int> v({1, 2, 3, 4});, i. e. explicit constructor call with initialiser list as argument) and in analogy to the well defined auto x(7); (a construct I won't ever use myself either...), I came up with the following:

auto x({1, 2, 3, 4});
// -> std::initializer_list<int> x({1, 2, 3, 4});

This compiled with GCC 7.2.0 (mingw64), but issued a warning (while the commented version again did not):

list-initializer for non-class type must not be parenthesized

I couldn't find anything relevant in the standard, so now the question is (out of pure interest...):

Why is this not allowed? (Is this covered by the standard or do we need to consider this a GCC bug?)

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • 1
    I'm surprised that `auto x0 = {1, 2, 3, 4};` is still allowed. – user7860670 Jul 04 '18 at 09:21
  • @VTT - To disallow it would break innocent constructs like `for(int i : {2, 3, 5, 7})` – StoryTeller - Unslander Monica Jul 04 '18 at 09:27
  • @VTT The [proposition](http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3681.html) (link provided by [chris](https://stackoverflow.com/users/962089/chris) in referenced question) that led to disallowing `auto x{1, 2}` actually prohibited the `=` variant as well, but standard committee did not follow at this part. On the other hand, if it had been forbidden, my variant above would have been the only valid one to create an initialiser list with auto, which new standard tries to avoid in favour to uniform initialisation, which probably is the reason to allow the mentioned variant... – Aconcagua Jul 04 '18 at 09:29
  • 1
    clang rejects this. – Baum mit Augen Jul 04 '18 at 09:30
  • @StoryTeller Because range base loop is mapped to code containing such an auto expression? Suppose there would be ways around... – Aconcagua Jul 04 '18 at 09:30
  • @Aconcagua - The initializer expression is mapped to a `auto&&` that is copy initialized (that's according to the standard). If by ways around you mean we can always define an array explicitly instead, sure. But such code exists in the wild because it's very tempting syntax. Breaking *that* seems ill-advised. I'm not saying it's *the* reason. But honestly, it's really hard to turn back the wheel on the entire list initialization syntax fiasco. – StoryTeller - Unslander Monica Jul 04 '18 at 09:33
  • @BaummitAugen OK, a hint to GCC not being buggy, not much influence on the question, though: Why warning error? – Aconcagua Jul 04 '18 at 09:37
  • @Aconcagua I'm not saying clang is right here, just stating a relevant observation. In the time I was willing to spend on this atm, I did not find a definite answer in the standard. – Baum mit Augen Jul 04 '18 at 09:40

1 Answers1

6

This is ill-formed. In short, braced-init-list can't be deduced in template argument deduction, it's considered as non-deduced context.

6) The parameter P, whose A is a braced-init-list, but P is not std::initializer_list or a reference to one:

Firstly, auto type deduction uses the rules of template argument deduction from a function call. [dcl.type.auto.deduct]/4

(emphasis mine)

If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initialization is copy-list-initialization, with std​::​initializer_­list<U>. Deduce a value for U using the rules of template argument deduction from a function call, where P is a function template parameter type and the corresponding argument is e. If the deduction fails, the declaration is ill-formed. [ Example:

const auto &i = expr;

The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:

template <class U> void f(const U& u);

— end example ]

Note that auto x({1, 2, 3, 4}); is direct initialization, not copy initialization, then the invented type template parameter is just U, not std​::​initializer_­list<U>, and the corresponding argument is {1, 2, 3, 4}.

And in template argument deduction from a function call, template parameter can't be deduced from braced-init-list. [temp.deduct.call]/1

Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std​::​initializer_­list or P'[N] for some P' and N and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument, and in the P'[N] case, if N is a non-type template parameter, N is deduced from the length of the initializer list. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context ([temp.deduct.type]). [ Example:

template<class T> void g(T);
g({1,2,3});                     // error: no argument deduced for T

— end example ]

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Wouldn't I have two template deductions? First `{1, 2, 3, 4}` creates a `std::initializer_list`, where int is deduced from the parameters, and only *then* the type of auto is deduced, where `U` now is `std::initializer_list`, i. e. something like this: `template void f(T t); auto x = {1, 2, 3, 4}; f(x);`? Or shorter: `auto x = {1, 2, 3, 4}; auto y(x);` (compiles without warning...)? – Aconcagua Jul 04 '18 at 09:52
  • @Aconcagua The standard doesn't say so; `{1, 2, 3, 4}` is used for deduction directly. And IMO that would make the rule of auto type deduction more complex.. – songyuanyao Jul 04 '18 at 09:55
  • So from `{1, 2, 3}`, *always* `int` is deduced, *unless* we have copy-list-initialisation? In consequence, `auto x({1, 2, 3})` is equivalent to the invalid `auto x{1, 2, 3};`? Then with `auto x({1});`, `decltype(x)` is `int`??? If so, GCC is buggy, as giving `std::initializer_list` *and* for not rejecting entirely... – Aconcagua Jul 04 '18 at 10:14
  • @Aconcagua `{1, 2, 3}` won't be deduced as `int`, in copy-list-initialization it'll be deduced as `std​::​initializer_­list`, in direct-list-initialization it's ill-formed. `{1}` is deduced as `int`, only when used in direct-list-initialization with `auto`, in copy-list-initialization it'll be deduced as `std​::​initializer_­list` too. And both `{1, 2, 3}` and `{1}` could not be deduced in template argument deduction. – songyuanyao Jul 04 '18 at 10:15
  • Just wait a second: Then `template void f(T t); f({1, 2, 3});` is ill-formed *as well* (i. e. copy-list-initialisation is the *only* way to deduce `std::initializer_list` from such expressions; apart from assigning to variables of explicit `std::initializer_list` type, of course)? – Aconcagua Jul 04 '18 at 10:21
  • At least one thing is sure already now: whether or not GCC is right issuing a warning (or clang rejecting), the diagnostic message of GCC is is pointing at the wrong direction... – Aconcagua Jul 04 '18 at 10:22
  • @Aconcagua Yes, I agree it's hard to grasp what it wants to say. Well, the standard just requires the compiler to [issue a diagnostic](https://stackoverflow.com/a/40807628/3309790) for ill-formed code, it doesn't requires *how*; so in theory both gcc and clang are correct in this case. – songyuanyao Jul 04 '18 at 10:23
  • 2
    @Aconcagua `template void f(T t); f({1, 2, 3});` is always ill-formed. Deducing `{...}` as `std::initializer_list<>` in copy-list-initialization is a special rule, only for `auto`. – songyuanyao Jul 04 '18 at 10:25
  • Ah, OK, now the final clue to understand... And GCC rejects as well... Must exist a second special rule, though: `std::initializer_list l = {1, 2, 3};` or `template void f(std::initializer_list t)`... Would appreciate adding `f({1, 2, 3})` being ill-formed to the answer as a side note, if possible with reference to the standard (for completeness...). – Aconcagua Jul 04 '18 at 10:39
  • @Aconcagua Sorry don't get it.. Do you mean they're well-formed? – songyuanyao Jul 04 '18 at 10:45
  • I meant: direct initialisation of an explicit `std::initializer_list` is (or seems to be at least, according to GCC) well-formed. Sorry myself, have been quite short on that... – Aconcagua Jul 04 '18 at 12:40
  • 1
    @Aconcagua As so often, I like to point out that an implementation is free to add as many _language extensions_ as it wants to. To quote from [(intro.compliance/8)](https://timsong-cpp.github.io/cppwp/n4659/intro.compliance): A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs. – Arne Vogel Jul 20 '18 at 06:47
  • IOW it's a bug, but the bug is not that a binary is produced, but that no diagnostic is emitted. Also, an implementation may (as documented) not be guaranteed to be standard conforming unless certain options are passed on the command line. For GCC in particular, `-pedantic` used to be a good way to improve standard compliance, though I'm not sure whether it still is. – Arne Vogel Jul 20 '18 at 06:53
  • 1
    @ArneVogel Well, a warning actually *was* issued (see question), so in this respect, no bug at all. – Aconcagua Jul 20 '18 at 06:58