允许为所有数组元素调用任何构造函数

时间:2018-02-04 17:19:19

标签: c++

经过一些调查后,看起来行为初始化变量遵循一些惯例。

对于单个元素:

auto* x = new int; // undefined value
auto* x = new int{}; // 0 (default initializer)
auto* x = new int(23); // 23 (copy initializer)

auto* x = new Test; // default constructor
auto* x = new Test{}; // default constructor
auto* x = new Test(...); // X constructor (overload-chosen)

这很有意义,直到你尝试在数组上应用相同的逻辑:

auto* x = new int[10]; // all undefined values - OK
auto* x = new int[10]{}; // all 0 (default initializer) - OK
auto* x = new int[10](23); // all 23 (copy initializer on all) - NOT IMPLEMENTED

auto* x = new Test[10]; // default constructors - OK
auto* x = new Test[10]{}; // default constructors - OK
auto* x = new Test[10](...); // X constructor on all (overload-chosen) - NOT IMPLEMENTED

我的逻辑说你可以做一些假设:

  • 如果可以使用T(Args..)构造类型,则可以构造数组中的每个元素。没有理由不允许T[N](Args...)语法。

有谁知道为什么这个功能不存在?允许

是非常好的
new int[10](23); // all 23
new Test[10]("asd", 123); // construct all using Test("asd", 123)

编辑:这样做的全部思路是避免默认初始化/构造函数并直接调用我们需要的那个。

2 个答案:

答案 0 :(得分:4)

如果在标准库 中有这样的东西会很好,那么你想要的功能相对简单易用:

namespace detail {
    template<typename T, typename... ArgTs, std::size_t... Is>
    constexpr auto make_filled_array(ArgTs const&... args, std::index_sequence<Is...>)
    -> std::array<T, sizeof...(Is)> {
        return {{(Is, T(args...))...}};
    }
}

template<typename T, std::size_t N, typename... ArgTs, std::enable_if_t<N != 0, int> = 0>
constexpr auto make_filled_array(ArgTs const&... args) {
    return detail::make_filled_array<T, ArgTs...>(args..., std::make_index_sequence<N>{});
}

// ...

auto arr1 = make_filled_array<int, 10>(23);
auto arr2 = make_filled_array<Test, 10>("asd", 123);

Online Demo

那就是说,我认为在这里采用构造函数参数没有任何意义;容器emplace函数很有用,因为它们完美转发,但我们不能在这里做,因为我们需要为每个构造函数调用重用参数(因此从它们移动不是一个选项)。我认为复制构造很自然:

namespace detail {
    template<typename T, std::size_t... Is>
    constexpr auto make_filled_array(T const& t, std::index_sequence<Is...>)
    -> std::array<T, sizeof...(Is)> {
        return {{(Is, t)...}};
    }
}

template<std::size_t N, typename T, std::enable_if_t<N != 0, int> = 0>
constexpr auto make_filled_array(T const& t) {
    return detail::make_filled_array(t, std::make_index_sequence<N>{});
}

// ...

auto arr1 = make_filled_array<10>(23);
auto arr2 = make_filled_array<10>(Test{"asd", 123});

Online Demo

N.b。使用new意味着您可能来自托管语言,需要阅读价值语义。 ; - ]此代码可以更改为返回std::array<T, N>*T*(或者甚至是std::unique_ptr<T[]>以获得适度合理的内容),但是,为什么会有一个... ?只需使用std::vector<T>

std::vector<int> vec(10, 23);

我选择在此处演示std::array<>,因为您的示例都具有恒定的大小。

最让人想到的是std::fill,但这不会初始化数组......

答案 1 :(得分:3)

我不能代表委员会发言,但我可以看到一个案例,你的语法非常有问题:从 temporaries:初始化。

X* x = new X[10](Y{});

你如何处理这个案子?

你不能为10个元素中的每个元素调用X(Y&&)(假设它存在),因为你只有1个临时元素,而第一个元素之后的所有元素都会从未指定状态的对象初始化。

另一种选择是以某种方式调用X(const Y&)(假设它再次存在)。除了使标准复杂化并添加意外的不一致行为,这只会打开另一种蠕虫。 X对象可以对我们的Y{}进行引用绑定,假设它是一个左值,并且最终会有一个悬空引用。

我看到唯一可行的解​​决方案是不允许对此语法进行右值引用,但这只会提供更多SO问题。

我认为标准委员会考虑到这一点是非常安全的。