Frankly, the subtleties of the various initialization techniques make it difficult to say that any one practice is a "good habit."
As mentioned in a comment, Scott Meyers discusses brace-initialization at length in Modern Effective C++. He has made further comments on the matter on his blog, for instance here and here. In that second post, he finally says explicitly that he thinks the morass of C++ initialization vagaries is simply bad language design.
As mentioned in 101010's answer, there are benefits to brace-initialization. The prevention of implicit narrowing is the main benefit, in my opinion. The "most vexing parse" issue is of course a genuine benefit, but it's paltry--it seems to me that in most cases an incorrect int a();
instead of int a;
would probably be caught at compile time.
But there are at least two major drawbacks:
- In C++11 and C++14,
auto
always deduces std::initializer_list
from a brace-initializer. In C++17, if there's only one element in the initialization list, and =
is not used, auto
deduces the type of that element; the behavior for multiple elements is unchanged (See the second blog post linked above for a clearer explanation, with examples.) (Edit: as pointed out by T.C. in a comment below, my understanding of the C++17
rules for auto
with brace initialization is still not quite right.) All of these behaviors are somewhat surprising and (in mine and Scott Meyers' opinions) annoying and perplexing.
- 101010's third listed benefit, which is that initialization list constructors are preferred to all other constructors, is actually a drawback as well as a benefit. You've already mentioned that the behavior of
std::vector<int> vi{ 5, 1 };
is surprising to people familiar with vector
's old two-element constructor. Scott Meyers lists some other examples in Effective Modern C++. Personally, I find this even worse than the auto
deduction behavior (I generally only use auto
with copy initialization, which makes the first issue fairly easy to avoid).
EDIT: It turns out that stupid compiler-implementation decisions can sometimes be another reason to use brace-initialization (though really the #undef
approach is probably more correct).