我有以下课程:
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
。
哪个对我的班级最有意义?
答案 0 :(得分:2)
使用{}
与()
确定调用哪个构造函数。
{}
会调用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";
}
旁注; initializer_list
上的cppreference page就是这种行为的一个很好的例子。
如果需要(即支持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
,类构造函数最终成为混淆的源头,并且这在对象构造期间呈现出来。 foo(1,2)
和foo{1,2}
之间存在差异;后者调用构造函数的initializer_list
形式。解决此问题的一种方法是使用“标记”来区分“普通”构造函数形式与initializer_list
形式。
答案 1 :(得分:2)
在这种情况下,因为类有一个带std::initializer_list
的构造函数,所以在工厂函数中使用{}
通常会改变你传入的任何参数列表的语义含义 - 通过转动任何包参数大于2的初始化列表。
对于该功能的用户来说,这将是令人惊讶的。
因此,请使用()
表单。想要传递initializer_list的用户可以通过调用
forward_args({ ... });
注意上面的语法工作,你需要提供另一个重载:
template <class T>
auto forward_args(std::initializer_list<T> li)
{
return foo(li).size();
}
答案 2 :(得分:1)
真的,你不应该像这样定义你的班级foo
(前提是它在你的控制范围内)。这两个构造函数有重叠,导致两个初始化的错误情况:
foo f1(3);
foo f2{3};
意思是两件不同的事情;需要编写转发功能的人面临无法解决的问题。 this blog post详细介绍了这一点。 std::vector
也遇到同样的问题。
相反,我建议您使用标记为&#39;构造:
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);
// ...
}
现在您没有重叠,您可以安全地使用{}变体。