C 배열 초기화 "int arr [] = {e1, e2, e3,…}"동작을 std :: array로 에뮬레이트하는 방법?


137

(참고 :이 질문은 요소 수를 지정하지 않아도 중첩 된 유형을 직접 초기화 할 수 있습니다.)
이 질문 은 C 배열의 왼쪽 사용법을 설명합니다 int arr[20];. 에 그의 대답 , C 배열의 마지막 거점의 @ 제임스 칸 세이 쇼 하나, 그것은 독특한 초기화 특성이있다 :

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

우리는 요소의 수를 지정할 필요가 없습니다. 이제 C ++ 11 함수 std::beginstd::endfrom <iterator>( 또는 자신의 변형 )을 사용하여 반복 하고 크기를 생각할 필요가 없습니다.

이제 같은 방법으로 달성 할 수있는 방법이 std::array있습니까? 매크로를 사용하면 더보기 좋게 만들 수 있습니다. :)

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

편집 : 다양한 답변으로 컴파일 된 중간 버전은 다음과 같습니다.

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

그리고 모든 종류의 멋진 C ++ 11을 사용합니다.

  • Variadic 템플릿
  • sizeof...
  • rvalue 참조
  • 완벽한 전달
  • std::array, 물론이야
  • 균일 한 초기화
  • 균일 한 초기화로 리턴 유형 생략
  • 타입 추론 ( auto)

그리고 예제는 여기 에서 찾을 수 있습니다 .

그러나 @Johannes가 @Xaade의 답변에 대한 의견에서 지적한 것처럼 이러한 함수로 중첩 유형을 초기화 할 수 없습니다. 예:

struct A{ int a; int b; };

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

또한 이니셜 라이저 수는 구현에서 지원하는 함수 및 템플릿 인수의 수로 제한됩니다.


다양한 방법. 그것은 할당과 같은 초기화가 아니지만 내가 올 수있는 가장 가깝습니다. 초기화하려면 메모리에 직접 액세스해야합니다.
리 루비 에르

분명히 C ++ 0x는 이니셜 라이저 구문을 지원합니다. 대박. 보다 복잡한 지원을위한 언어 지원을 통해 C #과 비슷 해지는 것과 같습니다. 인터페이스에 대한 공식적인 언어 지원이 있는지 아는 사람이 있습니까 ??
리 루비 에르

10
@Downvoter : 이유?
Xeo

1
사과, TMP당신 질문 의 의미는 무엇 입니까?
kevinarpe

1
@kevinarpe TMP는 아마도 템플릿 메타 프로그래밍을 의미합니다 .
BeeOnRope

답변:


63

내가 생각할 수있는 최선은 :

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

그러나이를 위해서는 컴파일러가 NRVO를 수행 한 다음 반환 된 값의 사본을 건너 뜁니다 (법적이지만 필수는 아님). 실제로 모든 C ++ 컴파일러가 직접 초기화만큼 빠르도록 최적화 할 수있을 것으로 기대합니다.


gcc 4.6.0은 두 번째 컴파일을 허용하지 않고 double에서 value_type으로의 변환을 좁히는 것에 대해 불평하지만 clang ++ 2.9는 둘 다 괜찮습니다!
Cubbi

21
Bjarne이 "새로운 언어와 같은 느낌"에 대해 말한 대부분의 것을 이해하는 것은 이와 같은 대답입니다. : 다양한 템플릿, 늦은 반환 지정자 및 유형 공제 올인원!
Matthieu M.

@Matthieu : 이제 @DeadMG의 코드에서 rvalue 참조, 완벽한 전달 및 균일 한 초기화를 추가하면 많은 새로운 기능이 설정됩니다. :>
Xeo

1
@Cubbi : 실제로 g ++은 바로 여기에 있습니다 .C ++ 0x의 집계 초기화에서는 좁은 변환이 허용되지 않습니다 (그러나 C ++ 03에서는 허용됩니다-내가 알지 못하는 주요 변경 사항!). 두 번째 make_array전화를 끊겠습니다 .
Pavel Minaev

@Cubbi, 그렇습니다. 그러나 그것은 명백한 변환입니다-또한 자동 다운 캐스트 및 기타 것들을 허용합니다. 이것은 여전히 암시 적으로 변환 할 수없는 static_assert경우를 감지하기 위해 일부 TMP를 사용하여 수행 할 수 있지만 을 사용 하면 남아 있습니다. 독자의 연습으로 :)TailTT(tail)...
Pavel Minaev

39

나는 간단한 것을 기대할 것이다 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
를 제거 std::array<ret, sizeof...(T)>return문. 이는 T&&C ++ 14 및 C ++ 11에 배열 유형의 이동 생성자가 (construct-from-과 달리) 강제로 존재하게합니다 .
Yakk-Adam Nevraumont

8
나는 C ++ 사람들이 :-) 간단한 전화를 어떻게 사랑
치로 틸리郝海东冠状病六四事件法轮功

20

이전 게시물의 몇 가지 아이디어를 결합한 다음은 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)...};
}

이상하게도 반환 값을 rvalue 참조로 만들 수 없으며 중첩 된 구성에는 작동하지 않습니다. 어쨌든, 여기 테스트가 있습니다 :

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]]]

(마지막 출력을 위해 pretty-printer를 사용하고 있습니다.)


실제로,이 구조의 유형 안전을 향상 시키십시오. 모든 유형이 동일해야합니다. 한 가지 방법은 위에서 편집 한 정적 어설 션을 추가하는 것입니다. 다른 방법은 make_array다음과 같이 유형이 동일한 경우 에만 활성화 하는 것입니다.

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

어느 쪽이든 variadic all_same<Args...>유형 특성 이 필요합니다 . 그것은 여기에서 일반화이다 std::is_same<S, T>(부패가 혼합 수 있도록하는 것이 중요합니다 T, T&, T const &등) :

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

참고 make_array()복사 - 임시 (충분한 최적화 플래그와 함께!)하는 컴파일러에 의해 반환이를 rvalue로 치료를 허용하거나 최적화 멀리, 그리고 std::array컴파일러가 최적의 시공 방법을 선택 무료입니다, 그래서 집계 유형이 .

마지막으로, make_array이니셜 라이저를 설정할 때 복사 / 이동 구성을 피할 수 없습니다 . 따라서 std::array<Foo,2> x{Foo(1), Foo(2)};복사 / 이동은 없지만 auto x = make_array(Foo(1), Foo(2));인수가 전달 될 때 두 개의 복사 / 이동이 make_array있습니다. 난 당신이 도우미에 전적으로 가변 인자 초기화 목록을 통과 할 수 없기 때문에 당신이 그 개선 할 수 있다고 생각하지 않습니다 를 추론 유형 및 크기 - 프리 프로세서가 있던 경우에 sizeof...가변 인수 기능을, 아마도 그 완료,하지만 수 핵심 언어 내에서.


13

후행 반환 구문을 사용하면 make_array더 간단해질 수 있습니다

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

불행히도 집계 클래스의 경우 명시 적 유형 지정이 필요합니다

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

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

실제로이 make_array구현은 sizeof ... 연산자로 나열됩니다 .


C ++ 17 버전

클래스 템플릿 제안에 대한 템플릿 인수 공제 덕분에 공제 가이드를 사용하여 make_array도우미를 제거 할 수 있습니다.

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

-std=c++1zx86-64 gcc 7.0 에서 플래그로 컴파일


6
C ++ 17이 이미 공제 가이드가 있어야합니다 en.cppreference.com/w/cpp/container/array/deduction_guides
underscore_d

6

이 질문을 한 후 꽤 오랜 시간이 걸렸지 만 기존 답변에 여전히 단점이 있다고 생각하므로 약간 수정 된 버전을 제안하고 싶습니다. 다음은 기존 답변이 누락 된 것으로 생각되는 요점입니다.


1. RVO에 의존 할 필요가 없음

일부 답변은 우리가 RVO에 의존하여 생성 된 것을 반환해야한다고 언급합니다 array. 사실이 아닙니다. 우리는 복사 목록 초기화 를 사용하여 임시가 생성되지 않도록 보장 할 수 있습니다. 따라서 대신 :

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

우리는해야합니다 :

return {{values}};

확인 2. 기능을make_arrayconstexpr

이를 통해 컴파일 타임 상수 배열을 만들 수 있습니다.

3. 모든 인수가 동일한 유형인지 확인하지 않아도됩니다.

먼저, 그렇지 않은 경우 목록 초기화는 범위를 좁힐 수 없으므로 컴파일러는 경고 또는 오류를 발생시킵니다. 둘째, 실제로 우리 자신의 static_assert일을 하기로 결정하더라도 (아마도 더 나은 오류 메시지를 제공하기 위해), 아마도 원시 유형보다는 인수의 붕괴 유형을 비교해야 할 것입니다 . 예를 들어

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

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

우리는 단순히 경우 static_assert그 보내고 a, b,와 c같은 유형이,이 검사는 실패 할 것이다, 그러나 아마 아니라는 것을 우리는 무엇을 기대할. 대신, 우리는 그들의 std::decay_t<T>유형 (모두 ints) 을 비교해야합니다 .

전달 된 인수를 소멸하여 배열 값 유형을 추론합니다.

이는 포인트 3과 유사합니다. 동일한 코드 스 니펫을 사용하지만 이번에는 값 유형을 명시 적으로 지정하지 마십시오.

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

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

우리는 아마도을 만들고 싶어 array<int, 3>하지만 기존 답변의 구현은 모두 그렇게하지 못할 것입니다. 우리가 할 수있는 것은 a std::array<T, …>를 반환하는 대신 a 를 반환하는 것 std::array<std::decay_t<T>, …>입니다.

이 접근법에는 단점이 하나 있습니다 array. 더 이상 cv-qualified 값 유형을 반환 할 수 없습니다 . 그러나 대부분의 경우와 같은 대신 어쨌든 array<const int, …>사용 const array<int, …>합니다. 절충이 있지만 합리적인 것으로 생각합니다. C ++ 17 std::make_optional도 다음과 같은 접근 방식을 취합니다.

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

위의 사항을 고려하면 make_arrayC ++ 14 의 전체 구현은 다음과 같습니다.

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

용법:

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

6

C ++ 11은 (대부분?) std 컨테이너에 대해 이러한 초기화 방식을 지원 합니다 .


1
그러나 OP는 배열의 크기를 지정하고 싶지 않지만 size는 std :: array의 템플릿 매개 변수입니다. 따라서 std :: array <unsigned int, 5> n = {1,2,3,4,5}와 같은 것이 필요합니다.
juanchopanza

std::vector<>명시 적 정수가 필요하지 않으며 왜 그런지 잘 모르겠습니다 std::array.
Richard

@Richard는 std :: vector의 크기가 동적이고 std :: array의 크기가 고정되어 있기 때문입니다. 참조 : en.wikipedia.org/wiki/Array_(C%2B%2B)
juanchopanza

@juanchopanza 그러나 {...}구문은 컴파일 타임 상수 범위를 암시하므로 ctor는 범위를 추론 할 수 있어야합니다.
Richard

1
std::initializer_list::sizeconstexpr함수 가 아니므로 이와 같이 사용할 수 없습니다. 그러나 libstdc ++ (GCC와 함께 제공되는 구현)에서 버전을 가질 계획이 있습니다 constexpr.
Luc Danton

5

(@dyp 솔루션)

참고 : C ++ 14 ( std::index_sequence) 가 필요합니다 . std::index_sequenceC ++ 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;
}

std :: array 요소의 기본 초기화를 간과했습니다. 현재 수정 사항을 찾고 있습니다.
Gabriel Garcia

@dyp 코드로 답변을 업데이트했습니다. 당신이 당신의 자신의 답변을 작성하기로 결정하면 알려주세요 그리고 나는 내 아래로 가져갑니다. 감사합니다.
Gabriel Garcia

1
아냐 괜찮아 길이를 추론하기 위해 임시 배열을 바인딩하는 것이 좋습니다. 내 코드가 컴파일되는지 확인하지 않았습니다. 나는 그것이 여전히 당신의 해결책이라고 생각하고 약간의 개선을 통해 대답합니다.)하지만 make_array강아지의 대답과 같이 변덕에 이점이 없다고 주장 할 수도 있습니다 .
dyp December

권리. 또한 템플릿은 질문의 요구 사항 중 하나 인 초기화 목록에서 유형을 추론 할 수 없습니다 (중괄호 초기화).
Gabriel Garcia

1

С ++ 17 컴팩트 구현.

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


0

어레이 메이커 유형을 만듭니다.

오버로드 operator,되어 각 요소를 이전 via 참조로 연결하는 표현식 템플릿을 생성합니다.

finish배열 작성기를 가져와 참조 체인에서 직접 배열을 생성 하는 무료 기능을 추가하십시오 .

구문은 다음과 같아야합니다.

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

그것은 {}단지 기반 건설을 허용하지 않습니다 operator=. 당신이 기꺼이 사용하려는 경우 =우리는 그것을 작동시킬 수 있습니다 :

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

또는

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

이들 중 어느 것도 좋은 솔루션처럼 보이지 않습니다.

Variardics를 사용하면 varargs 수에 대한 컴파일러의 제한으로 제한되고 {}하위 구조 에 대한 재귀 적 사용을 차단 합니다.

결국 좋은 해결책은 없습니다.

내가하는 일은 코드를 작성 T[]하여 std::array데이터를 무시 하고 소비합니다. 어떤 코드를 먹든 상관하지 않습니다. 때때로 이것은 전달 코드가 []배열을 std::array투명 하게 조심스럽게 바꿔야한다는 것을 의미 합니다.


1
"이것은 좋은 해결책처럼 보이지 않습니다." 내가 말하고 싶은 것은 : p
caps
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.