c++
Let's say I have a template that stores an object of type T
. I want to pass constructor arguments in order to initialize the data member. Should I use uniform-initialization or direct-initialization with non-curly braces?:
template<typename T>
struct X
{
template<typename... Args>
X(Args&&... args)
: t(std::forward<Args>(args)...) // ?
/* or */ : t{std::forward<Args>(args)...} // ?
private:
T t;
};
If the object I want to store is a std::vector
and I choose the curly-brace style (uniform-initialization) then the arguments I pass will be forwarded to the vector::vector(std::initializer_list<T>)
constructor, which may or may not be what I want.
On the other hand, if I use the non-curly brace style I loose the ability to add elements to the vector through its std::initializer_list
constructor.
What form of initialization should I use when I don't know the object I am storing and the arguments that will be passed in?
To be clear the ambiguity arises for types having multiple constructors, including one taking an std::initializer_list
, and another one whose parameters (when initialized with braces) may be interpreted as an std::initializer_list
by the compiler. That is the case, for instance, with std::vector<int>
:
template<typename T>
struct X1
{
template<typename... Args>
X1(Args&&... args)
: t(std::forward<Args>(args)...) {}
T t;
};
template<typename T>
struct X2
{
template<typename... Args>
X2(Args&&... args)
: t{std::forward<Args>(args)...} {}
T t;
};
int main() {
auto x1 = X1<std::vector<int>> { 42, 2 };
auto x2 = X2<std::vector<int>> { 42, 2 };
std::cout << "size of X1.t : " << x1.t.size()
<< "nsize of X2.t : " << x2.t.size();
}
(Note that the only difference is braces in X2
members initializer list instead of parenthesis in X1
members initializer list)
Output :
size of X1.t : 42
size of X2.t : 2
Demo
Standard Library authors faced this real problem when writing utility templates such as std::make_unique
, std::make_shared
or std::optional<>
(that are supposed to perfectly forward for any type) : which initialization form is to be preferred ? It depends on client code.
There is no good answer, they usually go with parenthesis (ideally documenting the choice, so the caller knows what to expect). Idiomatic modern c++11 is to prefer braced initialization everywhere (it avoids narrowing conversions, avoid c++ most vexing parse, etc..)
A potential workaround for disambiguation is to use named tags, extensively discussed in this great article from Andrzej's C++ blog :
namespace std{
constexpr struct with_size_t{} with_size{};
constexpr struct with_value_t{} with_value{};
constexpr struct with_capacity_t{} with_capacity{};
}
// These contructors do not exist.
std::vector<int> v1(std::with_size, 10, std::with_value, 6);
std::vector<int> v2{std::with_size, 10, std::with_value, 6};
This is verbose, and apply only if you can modify the ambiguous type(s) (eg types that expose constructors taking an std::initializer_list
and other constructors whose arguments list maybe converted to an std::initializer list
)
As with any initialization,
Use braces when the object contains a value, or several values which are getting piecewise initialized. This includes aggregate classes and numbers.
Braces appear more like a list of items.
Use parentheses when the object's initial state is computed from parameters.
Parentheses appear more like a sequence of function arguments.
This general rule includes the case of a container like std::vector<int>
which may be initialized with N copies of a number ( std::vector<int>(4,5)
) or a pair of numbers ( std::vector<int>{4,5}
).
By the way, since "uniform" initialization isn't really a catch-all, that term is discouraged. The official name is brace-initialization.
链接地址: http://www.djcxy.com/p/83016.html上一篇: 如何使用Rust编写本地Mac OS X GUI?
下一篇: C ++