我有以下类:
struct foo
{
std::size_t _size;
int* data;
public:
explicit foo(std::size_t s) : _size(s) { }
foo(std::size_t s, int v)
: _size(s)
{
data = new int[_size];
std::fill(&data[0], &data[0] + _size, v);
}
foo(std::initializer_list<int> d)
: _size(d.size())
{
data = new int[_size];
std::copy(d.begin(), d.end(), &data[0]);
}
~foo() { delete[] data; }
std::size_t size() const { return _size; }
};
我想提出这样的论点:
template <typename... Args>
auto forward_args(Args&&... args)
{
return foo{std::forward<Args>(args)...}.size();
//--------^---------------------------^
}
std::cout << forward_args(1, 2) << " " << forward_args(1) << " "
<< forward_args(2) << "n";
如果我用()
替换{}
,则输出为1 1 2
而不是2 1 1
。
哪一个对我的课最有意义?
在这种情况下,因为类有一个采用std::initializer_list
的构造函数,所以在工厂函数中使用{}
通常会改变传入的任何参数列表的语义,方法是将任何长于2的参数包变成initializer_list。
这将使该功能的用户感到惊讶。
因此,使用()
表单。想要传递initializer_list的用户可以通过调用来显式传递
forward_args({ ... });
注意,要使上述语法正常工作,您需要提供另一个重载:
template <class T>
auto forward_args(std::initializer_list<T> li)
{
return foo(li).size();
}
{}
与()
的使用决定了调用哪个构造函数。
-
{}
将调用表单foo(std::initializer_list<int> d)
,您将得到您所期望的结果。 -
()
将调用explicit foo(std::size_t s)
和foo(std::size_t s, int v)
,在所有情况下,第一个元素都是大小,所以给定参数,就会得到您看到的结果。
支持哪种形式取决于您希望forward_args
方法支持什么语义。如果希望参数按原样传递,则应使用()
(在这种情况下,用户需要首先提供initialiser_list
作为参数)。
可能(可能)您希望使用的形式是()
,使用如下;
template <typename... Args>
auto forward_args(Args&&... args)
{
return foo(std::forward<Args>(args)...).size();
//--------^---------------------------^
}
int main()
{
std::cout << forward_args(std::initializer_list<int>{1, 2}) << " "
<< forward_args(std::initializer_list<int>{3}) << " "
<< forward_args(std::initializer_list<int>{4}) << "n";
}
旁注;CCD_ 19上的cppreference页面就是这种行为的一个很好的例子。
如果需要(即支持forward_args({1,2})
),您可以为initializer_list
提供过载;
template <class Arg>
auto forward_args(std::initializer_list<Arg> arg)
{
return foo(std::move(arg)).size();
}
如前所述:给定initializer_list
,类构造函数最终是混乱的根源,这在对象构造过程中表现出来。CCD_ 23与CCD_;后者调用构造函数的CCD_ 25形式。解决这个问题的一种技术是使用"标记"来区分"普通"构造函数形式和initializer_list
形式。
实际上,您不应该这样定义类foo
(前提是它在您的控制范围内)。这两个构造函数有重叠,并导致两个初始化的糟糕情况:
foo f1(3);
foo f2{3};
意思是两种不同的东西;并且需要编写转发函数的人面临无法解决的问题。这在本博客文章中有详细介绍。CCD_ 28也存在同样的问题。
相反,我建议您使用一个"标记"构造函数:
struct with_size {}; // just a tag
struct foo
{
explicit foo(with_size, std::size_t s);
explicit foo(with_size, std::size_t s, int v);
foo(std::initializer_list<int> d);
// ...
}
现在您没有重叠,并且可以安全地使用{}变体。