Làm thế nào để mô phỏng khởi tạo mảng C, int int int [] = {e1, e2, e3, '}} Hành vi với std :: mảng?


137

(Lưu ý: Câu hỏi này là về việc không phải chỉ định số lượng phần tử và vẫn cho phép các kiểu lồng nhau được khởi tạo trực tiếp.)
Câu hỏi này thảo luận về việc sử dụng còn lại cho một mảng C như thế nào int arr[20];. Về câu trả lời của mình , @James Kanze cho thấy một trong những thành trì cuối cùng của mảng C, đó là đặc điểm khởi tạo độc đáo:

int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };

Chúng tôi không phải chỉ định số lượng các yếu tố, hoan hô! Bây giờ lặp lại nó với các chức năng C ++ 11 std::beginstd::endtừ <iterator>( hoặc các biến thể của riêng bạn ) và bạn không bao giờ cần phải nghĩ về kích thước của nó.

Bây giờ, có cách nào (có thể là TMP) để đạt được điều tương tự std::arraykhông? Sử dụng các macro được phép để làm cho nó trông đẹp hơn. :)

??? std_array = { "here", "be", "elements" };

Chỉnh sửa : Phiên bản trung gian, được tổng hợp từ nhiều câu trả lời khác nhau, trông như thế này:

#include <array>
#include <utility>

template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
  return { std::forward<T>(head), std::forward<Tail>(values)... };
}

// in code
auto std_array = make_array(1,2,3,4,5);

Và sử dụng tất cả các loại công cụ C ++ 11 thú vị:

  • Mẫu biến thể
  • sizeof...
  • tài liệu tham khảo giá trị
  • chuyển tiếp hoàn hảo
  • std::array, tất nhiên
  • khởi tạo thống nhất
  • bỏ qua kiểu trả về với khởi tạo thống nhất
  • kiểu suy luận ( auto)

Và một ví dụ có thể được tìm thấy ở đây .

Tuy nhiên , như @Johannes chỉ ra trong nhận xét về câu trả lời của @ Xaade, bạn không thể khởi tạo các loại lồng nhau với chức năng như vậy. Thí dụ:

struct A{ int a; int b; };

// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };

Ngoài ra, số lượng bộ khởi tạo được giới hạn ở số lượng đối số hàm và mẫu được hỗ trợ khi triển khai.


Phương pháp biến thể. Đó không phải là khởi tạo, giống như chuyển nhượng, nhưng đó là lần gần nhất tôi có thể đến. Để có được khởi tạo, bạn phải có quyền truy cập trực tiếp vào bộ nhớ.
Lee Louviere

Rõ ràng C ++ 0x hỗ trợ cú pháp khởi tạo. Tuyệt vời. Nó giống như trở nên giống C # hơn, với sự hỗ trợ ngôn ngữ cho sự hỗ trợ phức tạp hơn. Bất cứ ai cũng biết nếu chúng tôi nhận được hỗ trợ ngôn ngữ chính thức cho các giao diện ???
Lee Louviere

10
@Downvoter: Lý do?
Xèo

1
Lời xin lỗi, ý nghĩa của TMPcâu hỏi của bạn là gì?
kevinarpe

1
@kevinarpe TMP có lẽ là viết tắt của siêu lập trình mẫu .
BeeOnRope

Câu trả lời:


63

Điều tốt nhất tôi có thể nghĩ là:

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
     return a;
}

auto a = make_array(1, 2, 3);

Tuy nhiên, điều này đòi hỏi trình biên dịch phải thực hiện NRVO, và sau đó cũng bỏ qua bản sao của giá trị trả về (cũng hợp pháp nhưng không bắt buộc). Trong thực tế, tôi mong muốn bất kỳ trình biên dịch C ++ nào cũng có thể tối ưu hóa nó nhanh như khởi tạo trực tiếp.


gcc 4.6.0 không cho phép cái thứ hai biên dịch, phàn nàn về việc thu hẹp chuyển đổi từ double sang value_type, nhưng clang ++ 2.9 vẫn ổn với cả hai!
Cubbi

20
Với những câu trả lời như thế này, tôi hiểu hầu hết những gì Bjarne nói về cảm giác "giống như một ngôn ngữ mới" :) Các mẫu biến thể, công cụ xác định trả lại muộn và loại trừ tất cả trong một!
Matthieu M.

@Matthieu: Bây giờ thêm giới thiệu rvalue, chuyển tiếp hoàn hảo và khởi tạo thống nhất từ ​​mã của @ DeadMG và bạn đã có nhiều tính năng mới được thiết lập. :>
Xèo

1
@Cubbi: thực ra, g ++ ở ngay đây - thu hẹp chuyển đổi không được phép trong khởi tạo tổng hợp trong C ++ 0x (nhưng được phép trong C ++ 03 - một thay đổi đột phá mà tôi không biết!). Tôi sẽ xóa make_arraycuộc gọi thứ hai .
Pavel Minaev

@Cubbi, vâng, nhưng đó là một chuyển đổi rõ ràng - nó cũng sẽ cho phép truyền phát âm thầm và những thứ khác. Điều này vẫn có thể được thực hiện bằng cách sử dụng static_assertvà một số TMP để phát hiện khi nào Tailkhông thể chuyển đổi thành T, và sau đó sử dụng T(tail)..., nhưng điều đó còn lại như một bài tập cho người đọc :)
Pavel Minaev

39

Tôi mong đợi một sự đơn giản make_array.

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
    return { std::forward<T>(refs)... };
}

1
Tháo std::array<ret, sizeof...(T)>trên returntuyên bố. Điều đó vô nghĩa buộc một hàm tạo di chuyển trên kiểu mảng tồn tại (trái ngược với cấu trúc-từ- T&&) trong C ++ 14 và C ++ 11.
Yakk - Adam Nevraumont

7
Tôi thích cách mọi người gọi C ++ đơn giản như vậy :-)
Ciro Santilli 冠状 病 六四

20

Kết hợp một vài ý tưởng từ các bài viết trước, đây là một giải pháp hoạt động ngay cả đối với các công trình lồng nhau (được thử nghiệm trong GCC4.6):

template <typename T, typename ...Args>
std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
{
  static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
  return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};
}

Kỳ lạ thay, không thể làm cho giá trị trả về trở thành tham chiếu giá trị, điều đó sẽ không hoạt động đối với các công trình lồng nhau. Dù sao, đây là một bài kiểm tra:

auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
                    make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
                    make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
                    make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
                    );

std::cout << q << std::endl;
// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]

(Đối với đầu ra cuối cùng tôi đang sử dụng máy in đẹp của mình .)


Trên thực tế, hãy để chúng tôi cải thiện loại an toàn của công trình này. Chúng tôi chắc chắn cần tất cả các loại là giống nhau. Một cách là thêm một xác nhận tĩnh mà tôi đã chỉnh sửa ở trên. Một cách khác là chỉ bật make_arraykhi các loại giống nhau, như vậy:

template <typename T, typename ...Args>
typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
make_array(T && t, Args &&... args)
{
  return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};
}

Dù bằng cách nào, bạn sẽ cần all_same<Args...>đặc điểm loại hình biến đổi. Dưới đây là, khái quát hóa từ std::is_same<S, T>(lưu ý rằng mục nát là rất quan trọng để cho phép trộn của T, T&, T const &vv):

template <typename ...Args> struct all_same { static const bool value = false; };
template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
};
template <typename S, typename T> struct all_same<S, T>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
};
template <typename T> struct all_same<T> { static const bool value = true; };

Lưu ý rằng make_array()trả về bằng bản sao tạm thời, mà trình biên dịch (có cờ tối ưu hóa đủ!) Được phép coi là một giá trị hoặc tối ưu hóa đi, và std::arraylà một loại tổng hợp, vì vậy trình biên dịch có thể tự do chọn phương thức xây dựng tốt nhất có thể .

Cuối cùng, lưu ý rằng bạn không thể tránh sao chép / di chuyển xây dựng khi make_arraythiết lập trình khởi tạo. Vì vậy, std::array<Foo,2> x{Foo(1), Foo(2)};không có bản sao / di chuyển, nhưng auto x = make_array(Foo(1), Foo(2));có hai bản sao / di chuyển khi các đối số được chuyển tiếp đến make_array. Tôi không nghĩ rằng bạn có thể cải thiện điều đó, bởi vì bạn không thể chuyển một danh sách trình khởi tạo biến đổi từ vựng cho người trợ giúp suy ra loại và kích thước - nếu bộ tiền xử lý có sizeof...chức năng cho các đối số biến đổi, có lẽ điều đó có thể được thực hiện, nhưng không trong ngôn ngữ cốt lõi.


13

Sử dụng cú pháp trả về dấu make_arraycó thể được đơn giản hóa hơn nữa

#include <array>
#include <type_traits>
#include <utility>

template <typename... T>
auto make_array(T&&... t)
  -> std::array<std::common_type_t<T...>, sizeof...(t)>
{
  return {std::forward<T>(t)...};
}

int main()
{
  auto arr = make_array(1, 2, 3, 4, 5);
  return 0;
}

Không may cho các lớp tổng hợp, nó yêu cầu đặc tả kiểu rõ ràng

/*
struct Foo
{
  int a, b;
}; */

auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});

Trong thực tế, việc make_arraythực hiện này được liệt kê trong toán tử sizeof ...


phiên bản c ++ 17

Nhờ khấu trừ đối số mẫu cho đề xuất mẫu lớp, chúng tôi có thể sử dụng các hướng dẫn khấu trừ để thoát khỏi trình make_arraytrợ giúp

#include <array>

namespace std
{
template <typename... T> array(T... t)
  -> array<std::common_type_t<T...>, sizeof...(t)>;
}

int main()
{
  std::array a{1, 2, 3, 4};
  return 0; 
}

Được biên dịch với -std=c++1zcờ dưới x86-64 gcc 7.0


6
C ++ 17 nên có một hướng dẫn khấu trừ cho việc này: en.cppreference.com/w/cpp/container/array/dedraction_guides
underscore_d

6

Tôi biết đã khá lâu kể từ khi câu hỏi này được hỏi, nhưng tôi cảm thấy các câu trả lời hiện có vẫn còn một số thiếu sót, vì vậy tôi muốn đề xuất phiên bản sửa đổi một chút của mình. Sau đây là những điểm mà tôi nghĩ rằng một số câu trả lời hiện có bị thiếu.


1. Không cần phải dựa vào RVO

Một số câu trả lời đề cập rằng chúng ta cần dựa vào RVO để trả lại bản dựng array. Điều đó không đúng; chúng ta có thể sử dụng khởi tạo danh sách sao chép để đảm bảo sẽ không bao giờ có thời gian được tạo. Vì vậy, thay vì:

return std::array<Type, …>{values};

chúng ta nên làm:

return {{values}};

2. Tạo make_arraymột constexprchức năng

Điều này cho phép chúng ta tạo các mảng hằng số thời gian biên dịch.

3. Không cần kiểm tra xem tất cả các đối số có cùng loại không

Trước hết, nếu không, trình biên dịch sẽ đưa ra cảnh báo hoặc lỗi dù sao vì khởi tạo danh sách không cho phép thu hẹp. Thứ hai, ngay cả khi chúng ta thực sự quyết định làm việc của riêng mình static_assert(có lẽ để cung cấp thông báo lỗi tốt hơn), chúng ta vẫn có thể nên so sánh các loại phân rã của các đối số thay vì các loại thô. Ví dụ,

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array<int>(a, b, c);  // Will this work?

Nếu chúng ta chỉ đơn giản là static_assertlàm điều đó a, bccó cùng loại, thì kiểm tra này sẽ thất bại, nhưng đó có lẽ không phải là điều chúng ta mong đợi. Thay vào đó, chúng ta nên so sánh các std::decay_t<T>loại của chúng (tất cả đều intlà s)).

4. Giảm loại giá trị mảng bằng cách phân rã các đối số được chuyển tiếp

Điều này tương tự như điểm 3. Sử dụng cùng một đoạn mã, nhưng không chỉ định rõ ràng loại giá trị lần này:

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array(a, b, c);  // Will this work?

Chúng tôi có thể muốn thực hiện array<int, 3>, nhưng việc triển khai trong các câu trả lời hiện có có thể đều không thực hiện được. Những gì chúng ta có thể làm là, thay vì trả lại a std::array<T, …>, trả lại a std::array<std::decay_t<T>, …>.

Có một nhược điểm về cách tiếp cận này: chúng tôi không thể trả lại arrayloại giá trị đủ điều kiện cv nữa. Nhưng hầu hết thời gian, thay vì một cái gì đó như array<const int, …>, chúng tôi sẽ sử dụng một cách const array<int, …>nào đó. Có một sự đánh đổi, nhưng tôi nghĩ là hợp lý. C ++ 17 std::make_optionalcũng thực hiện phương pháp này:

template< class T > 
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );

Nếu tính đến các điểm trên, một triển khai hoạt động đầy đủ make_arraytrong C ++ 14 trông như thế này:

#include <array>
#include <type_traits>
#include <utility>

template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
    noexcept(noexcept(std::is_nothrow_constructible<
                std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
             >::value))

{
    return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}

template<typename T>
constexpr std::array<std::decay<T>_t, 0> make_array() noexcept
{
    return {};
}

Sử dụng:

constexpr auto arr = make_array(make_array(1, 2),
                                make_array(3, 4));
static_assert(arr[1][1] == 4, "!");

6

C ++ 11 sẽ hỗ trợ cách khởi tạo này cho (hầu hết?) Các thùng chứa std.


1
Tuy nhiên, tôi nghĩ OP không muốn chỉ định kích thước của mảng, nhưng kích thước là một tham số mẫu của std :: mảng. Vì vậy, bạn cần một cái gì đó như std :: mảng <unsign int, 5> n = {1,2,3,4,5};
juanchopanza

std::vector<>không cần số nguyên rõ ràng và tôi không chắc tại sao std::array.
Richard

@Richard, vì std :: vector có kích thước động và mảng std :: có kích thước cố định. Xem điều này: vi.wikipedia.org/wiki/Array_(C%2B%2B)
juanchopanza

@juanchopanza nhưng {...}cú pháp ngụ ý phạm vi không đổi thời gian biên dịch, vì vậy ctor có thể suy ra phạm vi.
Richard

1
std::initializer_list::sizekhông phải là một constexprchức năng và do đó không thể được sử dụng như thế này. Tuy nhiên, có các kế hoạch từ libstdc ++ (vận chuyển triển khai với GCC) để có phiên bản của chúng constexpr.
Luc Danton

5

(Giải pháp của @dyp)

Lưu ý: yêu cầu C ++ 14 ( std::index_sequence). Mặc dù người ta có thể thực hiện std::index_sequencetrong C ++ 11.

#include <iostream>

// ---

#include <array>
#include <utility>

template <typename T>
using c_array = T[];

template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
    return std::array<T, N>{{ std::move(src[Indices])... }};
}

template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
    return make_array(std::move(src), std::make_index_sequence<N>{});
}

// ---

struct Point { int x, y; };

std::ostream& operator<< (std::ostream& os, const Point& p) {
    return os << "(" << p.x << "," << p.y << ")";
}

int main() {
    auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});

    for (auto&& x : xs) {
        std::cout << x << std::endl;
    }

    return 0;
}

Tôi bỏ qua việc khởi tạo mặc định của các phần tử std ::. Hiện đang tìm kiếm một sửa chữa.
Gabriel Garcia

@dyp Tôi đã cập nhật câu trả lời với mã của bạn. Nếu bạn quyết định viết lên câu trả lời của riêng bạn, hãy cho tôi biết và tôi sẽ đưa tôi xuống. Cảm ơn bạn.
Gabriel Garcia

1
Không, nó ổn. Liên kết một mảng tạm thời để suy ra độ dài là ý tưởng của bạn và tôi đã không kiểm tra xem mã của tôi có biên dịch hay không. Tôi nghĩ rằng đó vẫn là giải pháp của bạn và trả lời, với một số sàng lọc;) Người ta có thể lập luận rằng mặc dù điều đó không có lợi cho một make_arraycâu trả lời như trong câu trả lời của Puppy.
dyp

Đúng. Hơn nữa, các mẫu không thể suy ra các loại từ danh sách trình khởi tạo, đây là một trong những yêu cầu của câu hỏi (khởi tạo được lồng ghép).
Gabriel Garcia

1

Thực hiện nhỏ gọn 17 ++.

template <typename... T>
constexpr auto array_of(T&&... t) {
    return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}

0

Nếu std :: mảng không phải là một ràng buộc và nếu bạn có Boost, thì hãy xem list_of(). Điều này không chính xác như khởi tạo mảng loại C mà bạn muốn. Nhưng gần gũi.


nó là cái tốt. và một câu hỏi tương tự về việc sử dụng nó để gán các cấu trúc lồng nhau có thể được tìm thấy ở đây Sử dụng-gán-map-list-of-for-Complex-type
Assambar

0

Tạo một kiểu tạo mảng.

Nó quá tải operator,để tạo ra một mẫu biểu thức kết nối từng phần tử với phần tử trước thông qua các tham chiếu.

Thêm một finishhàm miễn phí có trình tạo mảng và tạo một mảng trực tiếp từ chuỗi tham chiếu.

Cú pháp sẽ trông giống như thế này:

auto arr = finish( make_array<T>->* 1,2,3,4,5 );

Nó không cho phép {}xây dựng dựa trên, như chỉ operator=có. Nếu bạn sẵn sàng sử dụng, =chúng tôi có thể làm cho nó hoạt động:

auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );

hoặc là

auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );

Không ai trong số này trông giống như giải pháp tốt.

Việc sử dụng variardics giới hạn bạn đến giới hạn do trình biên dịch áp dụng đối với số lượng vararg và khối sử dụng đệ quy {}cho các cấu trúc con.

Cuối cùng, thực sự không phải là một giải pháp tốt.

Những gì tôi làm là tôi viết mã của tôi vì vậy nó tiêu thụ cả T[]std::arraydữ liệu agnostically - nó không quan tâm mà tôi nuôi nó. Đôi khi điều này có nghĩa là mã chuyển tiếp của tôi phải cẩn thận biến []các mảng thành std::arrays trong suốt.


1
"Chúng không giống như giải pháp tốt." Tôi cũng sẽ nói như vậy: p
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.