2016-03-27 46 views
8

Đây là một vấn đề thú vị mà tôi đang nghĩ về một thời gian trước đây. Cho một struct với một tổng hợp cơ bản:Khởi tạo tổng hợp phổ quát bởi các mẫu variadic

#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 
}; 

Làm thế nào bạn sẽ thực hiện variadic mẫu constructor A(const T& value, const Ts& ... values) để

  • chấp nhận cả hai giá trị của loại T và một A<T, N>
  • khởi động đúng tổng hợp cơ bản dựa trên các giá trị được đại diện bởi các đối số đã thông qua
  • tôn trọng năng lực của tổng
  • hỗ trợ C 14 quy tắC++ constexpr và không giới thiệu bất kỳ chi phí thời gian chạy

Đáp ứng các yêu cầu trên, chúng ta có thể làm như sau:

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; 
} 
+1

Giá trị kỳ vọng của 'y [2]' trong 'A y (1,2);' là gì? –

+0

Điều gì về chi phí thời gian chạy của lời gọi hàm tạo? –

+0

@VaughnCato cảm ơn cho việc bắt, đó là một loại sai, cập nhật bây giờ – plasmacel

Trả lời

9

Đây là một cách tiếp cận hoạt động, nhưng gần như chắc chắn có thể được cải thiện.

Chúng ta có một hàm tạo cho A lấy một gói tham số, chuyển đổi từng phần tử thành một bộ, nối các bộ với nhau để tạo một bộ lớn, và sau đó chỉ sử dụng tổng hợp khởi tạo từ bộ tuple lớn đó. Tất cả những điều sau đây có thể là constexpr, tôi chỉ bỏ qua nó một cách ngắn gọn.

tiên chúng ta thực hiện chuyển đổi:

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

Với:

// 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)...); 
} 

Và sau đó chúng ta chỉ cần khởi tạo từ đó:

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

+2

Điều duy nhất tôi muốn thêm là sử dụng 'forward_as_tuple' thay vào đó, để giảm sao chép . –

+0

@ T.C. Tốt, cảm ơn! Đã sửa. – Barry

5

Câu trả lời của @Barry chắc chắn là chính xác và có thể chấp nhận được. Nhưng nó đòi hỏi một số bổ sung thư viện C++ 14 (mà bạn có thể cũng có thể viết cho mình trong C++ 11), và tổng thể đòi hỏi một số tốt tuple - và chương trình meta fu.

Hãy xem nhiều đối số "phạm vi dải ô", trong đó phạm vi chỉ là một con trỏ và kích thước. Đối số vô hướng chỉ là một phạm vi kích thước-1 và các đối số A<T, N> là các phạm vi kích thước-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 };  
} 

Sau đó bạn có thể chỉ cần làm một vòng lặp đôi khắp các yếu tố của tất cả khoảng

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 làm việc tại thời gian biên dịch (yêu cầu GCC> = 6.0 hoặc 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  
} 
+0

Mặc dù phương pháp tiếp cận chính nó là hợp lệ và dễ theo dõi hơn một chút (do không có siêu lập trình mẫu), _ "không giới thiệu bất kỳ chi phí thời gian chạy" nào bị bỏ qua. – edmz

+0

@black cảm ơn, FTFY. Lưu ý rằng điều này đòi hỏi phải thay đổi thành viên dữ liệu từ 'std :: array ' thành mảng 'T [N]' C vì toán tử non-const '[]' cho trước đó không phải là 'constexpr'. – TemplateRex

Các vấn đề liên quan