Trailing comma in uniform initialization

1.7k views Asked by At

Is there any potential semantic difference when I use trailing comma during uniform initialization?

std::vector< std::size_t > v1{5, }; // allowed syntax
std::vector< std::size_t > v2{10};

Can I use trailing comma to make compiler to select std::vector::vector(std::initializer_list< std::size_t >) constructor instead of std::vector::vector(std::size_t, const std::size_t &) or are there any other tricks with mentioned syntax?

Can I use it to detect is there std::initializer_list-constructor overloading?

Considering the following code, which constructor must be selected?

struct A { A(int) { ; } A(double, int = 3) { ; } };
A a{1};
A b{2, };

This code is accepted by gcc 8 and A(int) is selected in both cases.

2

There are 2 answers

1
WhiZTiM On BEST ANSWER

First, The C++ grammar rules makes the trailing , optional for braced-init-list. To quote dcl.init/1

A declarator can specify an initial value for the identifier being declared. The identifier designates a variable being initialized. The process of initialization described in the remainder of [dcl.init] applies also to initializations specified by other syntactic contexts, such as the initialization of function parameters ([expr.call]) or the initialization of return values ([stmt.return]).

initializer:
  brace-or-equal-initializer
  ( expression-list )
brace-or-equal-initializer:
  = initializer-clause
  braced-init-list
initializer-clause:
  assignment-expression
  braced-init-list
braced-init-list:
  { initializer-list ,opt }
  { designated-initializer-list ,opt }
  { }

Secondly, you can't pretty much override the overload resolution system. It will always use the std::initializer_list constructor if you use such syntax and such std::initializer_list constructor is available.

dcl.init.list/2:

A constructor is an initializer-list constructor if its first parameter is of type std​::​initializer_­list or reference to possibly cv-qualified std​::​initializer_­list for some type E, and either there are no other parameters or else all other parameters have default arguments. [ Note: Initializer-list constructors are favored over other constructors in list-initialization ([over.match.list])....


The program below prints Using InitList:

#include <iostream>
#include <initializer_list>

struct X{
    X(std::initializer_list<double>){ std::cout << "Using InitList\n"; }
    X(int){ std::cout << "Using Single Arg ctor\n"; }
};

int main(){
    X x{5};
}

Despite the fact that 5 is a literal of type int, it should have made sense to select the single argument constructor since its a perfect match; and the std::initializer_list<double> constructor wants a list of double. However, the rules favour std::initializer_list<double> because its an initializer-list constructor.

As a result, even the program below fails because of narrowing conversion:

#include <iostream>
#include <initializer_list>

struct Y{
    Y(std::initializer_list<char>){ std::cout << "Y Using InitList\n"; }
    Y(int, int=4){ std::cout << "Y Using Double Arg ctor\n"; }
};

int main(){
    Y y1{4777};
    Y y2{577,};
    Y y3{57,7777};
}

In response to your comment below, "what if there is no overloading with std::initializer_list, or it is not the first constructor's parameter?" - then overload resolution doesn't choose it. Demo:

#include <iostream>
#include <initializer_list>

struct Y{
    Y(int, std::initializer_list<double>){ std::cout << "Y Using InitList\n"; }
    Y(int, int=4){ std::cout << "Y Using Double Arg ctor\n"; }
};

int main(){
    Y y1{4};
    Y y2{5,};
    Y y3{5,7};
}

Prints:

Y Using Double Arg ctor
Y Using Double Arg ctor
Y Using Double Arg ctor

If there is no initializer-list constructor available, then the {initializer-list...,} initializer pretty much falls back to direct initialization as per dcl.init/16, whose semantics are covered by the proceeding paragraph of dcl.init/16

0
Dúthomhas On

No. That comma is a concession to make preprocessor macro tricks work without compile errors. It means nothing about your data type or its size.