14

我有以下课程:

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.

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

4

3 回答 3

2

{}vs.的使用()决定了调用哪个构造函数。

  • {}调用表单foo(std::initializer_list<int> d),您将获得您似乎期望的结果。

  • ()调用explicit foo(std::size_t s)and foo(std::size_t s, int v),在所有情况下,第一个元素是大小,所以给定参数,你会得到你看到的结果。

支持哪种形式取决于您希望该forward_args方法支持什么语义。如果您希望“按原样”传递参数,()则应使用 (在这种情况下,用户需要提供 aninitialiser_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";
}

旁注;上的cppreference 页面initializer_list就是这种行为的一个很好的例子。

如果需要(即支持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表单。

于 2016-05-11T10:03:03.597 回答
2

在这种情况下,因为该类有一个带有 的构造函数,所以在工厂函数中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();
}
于 2016-05-11T10:03:18.880 回答
1

真的,你不应该这样定义你的类foo(只要它在你的控制范围内)。这两个构造函数有重叠并导致两个初始化的糟糕情况:

foo f1(3);
foo f2{3};

意味着两种不同的东西;而需要编写转发函数的人面临着无法解决的问题。此博客文章对此进行了详细介绍。也std::vector遇到同样的问题。

相反,我建议您使用“标记”构造函数:

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

    // ...
}

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

于 2016-05-11T11:04:54.743 回答