Rule A8-5-4 (advisory, implementation, automated)

If a class has a user-declared constructor that takes a parameter of type std::initializer_list, then it shall be the only constructor apart from special member function constructors.

Rationale

If an object is initialized using {} braced-initialization, the compiler strongly prefers constructor taking parameter of type std::initializer_list to other constructors. Thus, if it is defined in the class, it is initially a sole member of the candidate set of the two-phase overload resolution. Only if no viable std::initializer_list is found, the rest of constructors are considered in the second overload resolution. Such a case can be non-intuitive for developers and can lead to reviewers’ confusion on which constructor was intended to be called. If other constructors (besides the std::initializer_list one and special member functions) are declared in a class, then it is suggested to use, e.g. the std::vector( {1,1} ) syntax instead of std::vector v{1, 1}, which makes the intent clear.

Example

// $Id: A8-5-4.cpp 319328 2018-05-15 10:30:25Z michal.szczepankiewicz $
#include <cstdint>
#include <initializer_list>
#include <vector>

#include <iostream>

//non-compliant, there are other constructors
//apart from initializer_list one defined
class A
{
public:
A() = default;
A(std::size_t num1, std::size_t num2) : x{num1}, y{num2} {}
A(std::initializer_list<std::size_t> list) : x{list.size()}, y{list.size()} {
}
private:
std::size_t x;
std::size_t y;
};

class B
{
public:
B() = default;
B(std::initializer_list<std::size_t> list) : collection{list} { }

private:
std::vector<std::size_t> collection;

};

void F1() noexcept
{
A a1{};
A a2{{}};
A a3{0, 1};
recommended
A a4({0, 1});//
A a5(0, 1); //
by exception
}
void F2() noexcept
{
B b1{};
B b2{{}};
B b3{1, 2};
recommended
B b4({1, 2});
recommended
}

// Calls A::A()
// Calls A::A(std::initializer_list<std::size_t>)
// Calls A::A(std::initializer_list<std::size_t>), not
Calls A::A(std::initializer_list<std::size_t>), recommended
Calls A::A(std::size_t, std::size_t), compliant with A8-5-2

// Calls B::B()
// Calls B::B(std::initializer_list<std::size_t>)
// Calls B::B(std::initializer_list<std::size_t>), not
// Calls B::B(std::initializer_list<std::size_t>),

See also

Effective Modern C++ [13]: Item 7. Distinguish between () and {} when creating objects. ISO/IEC 14882:2014 [3]: 13.3.1.7: [over.match.list]