18
votes

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
The 1st one (std::initializer_list-constructor) is always selected. wandbox.org/permlink/Yy6VtcK5ISCbzZJp - songyuanyao
Anyway, there is no reason for v2{...} to call any constructor but the one taking std::initializer_list....only v2(...) will call std::vector::vector(std::size_t...) - jpo38
@songyuanyao Yes, but what if there is only constructors with unary and binary arities with, possible, default function parameter initializers and other circumstances? Is there any semantic difference at all in all thinkable cases? - Tomilov Anatoliy
@jpo38 actually both v1 and v2 will call initializer_list overload, see here: stackoverflow.com/questions/9723164/… - marcinj
@StoryTeller the OP changed the question while I was editing. I didn't just make a random change for fun. But still my bad. Thanks for picking it up and correcting. - jwpfox

2 Answers

12
votes

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

2
votes

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.