转发参数时我应该使用()还是{}?

时间:2016-05-11 09:48:53

标签: c++ c++11 uniform-initialization

我有以下课程:

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

哪个对我的班级最有意义?

3 个答案:

答案 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);

    // ...
}

现在您没有重叠,您可以安全地使用{}变体。