【问题标题】:Universal aggregate initialization by variadic templates可变参数模板的通用聚合初始化
【发布时间】:2016-07-14 23:47:46
【问题描述】:

这是我不久前正在考虑的一个有趣的问题。 给定一个带有底层聚合的 struct

#include <array>

template <typename T, size_t N>
struct A
{
   constexpr A() = default;

   template <typename ... Ts>
   constexpr A(const T& value, const Ts& ... values); // magic

   std::array<T, N> arr; // aggregate
};

你将如何实现可变参数模板构造函数A(const T&amp; value, const Ts&amp; ... values)

  • 接受T 和另一个A&lt;T, N&gt; 类型的值
  • 根据传递的参数表示的值正确初始化底层聚合
  • 尊重聚合的容量
  • 支持 C++14 constexpr 规则并且不会引入任何运行时开销

满足以上要求,可以做到以下几点:

int main()
{
   A<int, 3> x(1, 2, 3);
   A<int, 2> y(1, 2);

   A<int, 6> a(x, 1, 2, 3);
   A<int, 6> b(1, x, 2, 3);
   A<int, 6> c(1, 2, x, 3);
   A<int, 6> d(1, 2, 3, x);
   A<int, 6> e(x, x);
   A<int, 6> f(y, y, y);

   return 0;
}

【问题讨论】:

  • y[2]A&lt;int,3&gt; y(1,2); 中的期望值是多少?
  • 构造函数调用的运行时开销如何?
  • @VaughnCato 感谢您的发现,这是一个错误的输入,现在更新
  • @WojciechFrohmberg a constexpr 意味着内联,所以我们不能谈论调用

标签: c++ templates c++11 c++14 variadic-templates


【解决方案1】:

这是一种行之有效的方法,但几乎肯定可以改进。

我们有一个A 的构造函数,它接受一个参数包,将每个元素转换为一个元组,将这些元组连接在一起形成一个大元组,然后简单地使用该大元组的聚合初始化。以下都可以是constexpr,为了简洁我省略了。

首先我们进行转换:

template <class... Us>
A(Us const&... us)
: A(std::tuple_cat(as_tuple(us)...))
{ }

与:

// single argument
template <class U>
auto as_tuple(U const& u) {
    return std::forward_as_tuple(u);
}

// aggregate argument
template <size_t M>
auto as_tuple(A<T, M> const& a) {
    return as_tuple(a, std::make_index_sequence<M>{});
}

template <size_t M, size_t... Is>
auto as_tuple(A<T, M> const& a, std::index_sequence<Is...> ) {
    return std::forward_as_tuple(std::get<Is>(a.arr)...);
}

然后我们就从那里初始化:

template <class... Us, class = std::enable_if_t<(sizeof...(Us) <= N)>>
A(std::tuple<Us...> const& t)
: A(t, std::index_sequence_for<Us...>{})
{ }

template <class... Us, size_t... Is>
A(std::tuple<Us...> const& t, std::index_sequence<Is...> )
: arr{{std::get<Is>(t)...}}
{ }

Demo

【讨论】:

  • 我唯一要添加的是使用forward_as_tuple,以减少复制。
【解决方案2】:

@Barry 的回答当然是正确且可以接受的。但它需要添加一些 C++14 库(您可能也可以自己用 C++11 编写),并且总体上需要一些好的 tuple- 和元编程功能。

让我们将多个参数视为“范围范围”,其中范围只是一个指针和一个大小。标量参数只是大小为 1 的范围,A&lt;T, N&gt; 参数是大小为 N 的范围。

template<class T>
struct Range
{
    T const* data_;
    std::size_t size_;

    constexpr T const* begin() const noexcept { return data_; }
    constexpr T const* end() const noexcept { return data_ + size_; }
    constexpr std::size_t size() const noexcept { return size_; }
};

template<class T>
constexpr Range<T> as_range(T const& t)
{
    return { &t, 1 };
}

template<class T, std::size_t N>
struct A;

template<class T, std::size_t N>
constexpr Range<T> as_range(A<T, N> const& a)
{
    return { a.arr, N };    
}

然后您可以简单地对所有范围的所有元素进行双重循环

template <typename T, size_t N>
struct A
{
    T arr[N]; // aggregate

    constexpr A() = default;

    template <typename U, typename... Us>
    constexpr A(U const u, Us const&... us)
    : 
        arr{}
    {
        Range<T> rngs[1 + sizeof...(Us)] { as_range(u), as_range(us)... };
        auto i = 0;
        for (auto const& r : rngs)
            for (auto const& elem : r)
                arr[i++] = elem;
        assert(i == N);                
    }
};

Live Example 在编译时工作(需要 GCC >= 6.0 或 Clang >= 3.4)

template <class T, size_t N>
void print(A<T, N> const& a) {
    for (T const& t : a.arr) {
        std::cout << t << ' ';
    }
    std::cout << '\n';
}

int main()
{
    constexpr A<int, 3> x(1, 2, 3);
    constexpr A<int, 2> y(1, 2);

    constexpr A<int, 6> a(x, 1, 2, 3);
    constexpr A<int, 6> b(1, x, 2, 3);
    constexpr A<int, 6> c(1, 2, x, 3);
    constexpr A<int, 6> d(1, 2, 3, x);
    constexpr A<int, 6> e(x, x);
    constexpr A<int, 6> f(y, y, y);

    print(a); // 1 2 3 1 2 3
    print(b); // 1 1 2 3 2 3
    print(c); // 1 2 1 2 3 3 
    print(d); // 1 2 3 1 2 3
    print(e); // 1 2 3 1 2 3
    print(f); // 1 2 1 2 1 2    
}

【讨论】:

  • 虽然该方法本身是有效的并且在某种程度上更容易遵循(由于没有模板元编程),但 “不引入任何运行时开销” 规范被遗漏了.
  • @black 谢谢,FTFY。请注意,这需要将数据成员从 std::array&lt;T,N&gt; 更改为 T[N] C 数组,因为前者的非 const operator[] 不是 constexpr
猜你喜欢
  • 2021-08-20
  • 2017-05-15
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
相关资源
最近更新 更多