Std :: array ile C dizi başlatma “int arr [] = {e1, e2, e3,…}” davranışını nasıl taklit edebilirim?


137

(Not: Bu soru, öğelerin sayısını belirtmemek ve yine de iç içe türlerin doğrudan başlatılmasına izin vermek zorunda değildir.)
Bu soru , C dizisi için kalan kullanımları tartışmaktadır int arr[20];. On onun cevabı , C diziler son kalelerinden @James Kanze gösterileri biri, benzersiz başlatma özellikleri var:

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

Eleman sayısını belirtmek zorunda değiliz, yaşasın! Şimdi yinelerler C ++ 11 fonksiyonları ile bitti std::beginve std::endgelen <iterator>( veya kendi varyantları ) ve asla hatta kendi boyutunda düşünmek gerekir.

Şimdi, bunu başarmanın (muhtemelen TMP) yolları var std::arraymı? Makroların daha güzel görünmesine izin verilir. :)

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

Düzenleme : Çeşitli cevaplardan derlenen ara sürüm şu şekilde görünür:

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

Ve her türlü havalı C ++ 11 işini kullanır:

  • Değişken Şablonlar
  • sizeof...
  • rvalue referansları
  • mükemmel yönlendirme
  • std::array, elbette
  • düzgün başlatma
  • Tek tip başlatma ile geri dönüş tipinin çıkarılması
  • tür çıkarımı ( auto)

Ve burada bir örnek bulabilirsiniz .

Bununla birlikte , @Johannes @ Xaade'nin cevabındaki yorumda belirttiği gibi, yuvalanmış türleri böyle bir işlevle başlatamazsınız. Misal:

struct A{ int a; int b; };

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

Ayrıca, başlatıcı sayısı, uygulama tarafından desteklenen işlev ve şablon bağımsız değişkenlerinin sayısı ile sınırlıdır.


Değişken yöntem. Başlatma değil, ödev gibi, ama gelebileceğim en yakın şey. Başlangıç ​​durumuna getirmek için belleğe doğrudan erişiminizin olması gerekir.
Lee Louviere

Görünüşe göre C ++ 0x başlatıcı sözdizimini destekler. Muhteşem. Daha karmaşık destek için dil desteği ile daha çok C # gibi olmak gibi. Arayüzler için resmi dil desteği alıp almadığımızı bilen var mı ???
Lee Louviere

10
@Downvoter: Sebep?
Xeo

1
Özür dilerim, TMPsorunuzun anlamı nedir?
kevinarpe

1
@kevinarpe TMP muhtemelen şablon meta programlaması anlamına gelir .
BeeOnRope

Yanıtlar:


63

Düşünebileceğim en iyi şey:

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

Ancak, bu derleyicinin NRVO yapmasını gerektirir ve sonra döndürülen değerin kopyasını atlar (bu da yasaldır ancak zorunlu değildir). Uygulamada, herhangi bir C ++ derleyicisinin doğrudan başlatma kadar hızlı olacak şekilde optimize edebilmesini beklerim.


gcc 4.6.0, ikincinin derlenmesine izin vermez, dönüşümü double'dan value_type'a daraltmaktan şikayet eder, ancak clang ++ 2.9 her ikisinde de sorun yok!
Cubbi

21
Bjarne'nin "yeni bir dil gibi" hissi hakkında söylediklerinin çoğunu anladığım gibi cevaplar var :) Variadic şablonlar, geç dönüş belirleyici ve tür kesinti all-in-one!
Matthieu M.

@Matthieu: Şimdi @ DeadMG kodundan rvalue referansları, mükemmel yönlendirme ve tek tip başlatma ekleyin ve birçok yeni özellik ayarladınız. :>
Xeo

1
@Cubbi: aslında, g ++ tam burada - C ++ 0x'de toplam başlatmada daraltma dönüşümlerine izin verilmiyor (ancak C ++ 03'te izin verildi - kırılmayan bir değişiklik! İkinci make_arrayçağrıyı kaldıracağım .
Pavel Minaev

O da hala kullanılarak yapılabilir sessiz downcasts ve diğer bu tür things.This izin verecek - @Cubbi, evet, ama bu açık bir dönüşümdür static_assertalgılamak için ve bazı TMP Tailörtük olarak dönüştürülebilir değildir Tve sonra kullanarak T(tail)..., ama bu bırakılır okuyucu için bir egzersiz olarak :)
Pavel Minaev

39

Ben basit bir beklenebilir 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
Kaldır std::array<ret, sizeof...(T)>üzerine returnaçıklamada. Bu T&&, c ++ 14 ve C ++ 11'de dizi türündeki bir hareket yapıcısını (bir yapıdan farklı olarak) var olmaya zorlar .
Yakk - Adam Nevraumont

8
C ++ insanların bu kadar basit
dediğini seviyorum

20

Önceki yayınlardan birkaç fikri birleştirerek, iç içe yapılar için bile çalışan bir çözüm (GCC4.6'da test edilmiştir):

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

Garip bir şekilde, dönüş değerini iç içe yapılar için çalışmayan bir değer referansı yapamaz. Her neyse, işte bir test:

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

(Son çıktı için güzel yazıcımı kullanıyorum .)


Aslında, bu yapının tip güvenliğini geliştirelim. Kesinlikle aynı olmak için tüm tiplere ihtiyacımız var. Bunun bir yolu, yukarıda düzenlediğim statik bir iddia eklemektir. Diğer yol, sadece make_arraytürler aynı olduğunda etkinleştirmektir , şöyle:

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

Her iki durumda da, değişken all_same<Args...>tip özelliğine ihtiyacınız olacak . İşte genelleme olduğu std::is_same<S, T>(çürüyen karışmasına olanak vermek üzere önemli olduğunu not T, T&, T const &vs.):

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

O Not make_array()kopyalamaya karşı-geçici (yeterli optimizasyon bayraklarıyla!) Hangi derleyici tarafından döner bir rvalue olarak tedavi izin verilir veya başka optimize uzaklıkta ve std::arrayderleyici mümkün olan en iyi inşaat yöntemi seçmek için serbest kalsın, bir agrega türüdür .

Son olarak, make_arraybaşlatıcıyı ayarlarken kopyalama / taşıma yapısından kaçınamayacağınızı unutmayın . Yani std::array<Foo,2> x{Foo(1), Foo(2)};kopya / taşıma yok, ancak auto x = make_array(Foo(1), Foo(2));argümanlar iletildikçe iki kopya / taşıma var make_array. Bunu geliştirebileceğinizi düşünmüyorum, çünkü değişken bir başlatıcı listesini yardımcıya sözlü olarak aktaramazsınız ve tür ve boyutu çıkartamazsınız - eğer önişlemcinin sizeof...varyasyon argümanları için bir işlevi varsa , belki de yapılabilirdi, ama değil temel dilde.


13

Sondaki dönüş sözdizimini kullanmak make_arraydaha da basitleştirilebilir

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

Toplam sınıflar için talihsiz bir şekilde açık tip belirtimi gerektirir

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

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

Aslında bu make_arrayuygulama sizeof ... operatörü olarak listelenir


c ++ 17 sürümü

Sayesinde sınıf şablonları için şablon argümanı kesinti biz kesinti kılavuzları kullanabilirsiniz teklifin kurtulmak için make_arrayyardımcı

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

İle Derleyen -std=c++1zx86-64 gcc 7.0 altında bayrak


6
C ++ 17 bunun için zaten bir kesinti kılavuzuna sahip olmalıdır: en.cppreference.com/w/cpp/container/array/deduction_guides
underscore_d

6

C ++ 11 (en çok?) Std kaplar için bu başlatma yöntemini destekleyecektir .


1
Ancak, ben OP dizinin boyutunu belirtmek istemiyor düşünüyorum, ama boyutu std :: dizi bir şablon parametresidir. Yani std :: array <unsigned int, 5> n = {1,2,3,4,5};
juanchopanza

std::vector<>açık tamsayıya ihtiyaç duymaz ve neden std::arrayolacağını bilmiyorum .
Richard

@Richard, çünkü std :: vector dinamik boyuta ve std :: dizi sabit boyuta sahiptir.
Şuna

@juanchopanza ancak {...}sözdizimi derleme zamanı sabit boyutunu ifade eder, bu nedenle ctor kapsamı çıkartabilmelidir.
Richard

1
std::initializer_list::sizebir constexprişlev değildir ve bu nedenle bu şekilde kullanılamaz. Bununla birlikte, libstdc ++ 'dan (GCC ile gönderim uygulaması) sürümlerine sahip olma planları vardır constexpr.
Luc Danton

6

Bu sorunun sorulmasından bu yana biraz zaman geçtiğini biliyorum, ama mevcut cevapların hala bazı eksiklikleri olduğunu hissediyorum, bu yüzden biraz değiştirilmiş versiyonumu önermek istiyorum. Bazı mevcut cevapların eksik olduğunu düşündüğüm noktalar aşağıdadır.


1. RVO'ya güvenmeye gerek yok

Bazı cevaplar inşa edilen geri dönmek için RVO güvenmek gerektiğini belirtti array. Bu doğru değil; asla geçici olarak oluşturulmayacağını garanti etmek için kopya listesi başlatma işlemini kullanabiliriz . Bunun yerine:

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

yapmalıyız:

return {{values}};

2. make_arrayBir constexprişlev yapın

Bu, derleme zamanı sabit dizileri oluşturmamızı sağlar.

3. Tüm argümanların aynı türde olup olmadığını kontrol etmeye gerek yoktur

İlk önce, eğer değilse, derleyici yine de bir uyarı veya hata verecektir, çünkü liste başlatma daralmaya izin vermez. İkincisi, kendi işimizi gerçekten yapmaya karar versek bile static_assert(belki daha iyi hata mesajı vermek için), muhtemelen argümanların çürümüş türlerini ham türler yerine karşılaştırmalıyız . Örneğin,

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

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

Biz sadece Eğer static_assertbu ing a, bve caynı türde, o zaman bu onay başarısız olur, ama muhtemelen olmadığını biz ne beklenir. Bunun yerine, std::decay_t<T>türlerini (hepsi ints) karşılaştırmalıyız.

4. İletilen bağımsız değişkenleri bozarak dizi değeri türünü bulun

Bu, 3. noktaya benzer. Aynı kod snippet'ini kullanarak, ancak bu kez değer türünü açıkça belirtmeyin:

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

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

Muhtemelen bir şey yapmak istiyoruz array<int, 3>, ancak mevcut cevaplardaki uygulamalar muhtemelen bunu yapamaz. Yapabileceğimiz şey, a std::array<T, …>dönmek yerine a std::array<std::decay_t<T>, …>.

Bu yaklaşımın bir dezavantajı var: arraycv-nitelikli bir değer türüne artık geri dönemeyiz. Ama çoğu zaman, bir şey yerine, yine de array<const int, …>kullanırdık const array<int, …>. Bir değiş tokuş var, ama bence makul bir şey. C ++ 17 std::make_optionalde bu yaklaşımı benimser:

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

Yukarıdaki hususlar dikkate alındığında make_array, C ++ 14'te tam çalışan bir uygulama şöyle görünür:

#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>, 0> make_array() noexcept
{
    return {};
}

Kullanımı:

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

5

(@Dyp tarafından çözüm)

Not: C ++ 14 ( std::index_sequence) gerektirir . Her ne kadar biri std::index_sequenceC ++ 11 uygulamak olabilir .

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

Ben std :: dizi öğelerin varsayılan başlatma gözardı. Şu anda bir düzeltme arıyorum.
Gabriel Garcia

@dyp Cevabı kodunuzla güncelledim. Kendi cevabınızı yazmaya karar verirseniz, bana bildirin, ben de benimkini yıkacağım. Teşekkür ederim.
Gabriel Garcia

1
Hayır, bu iyi. Uzunluğu çıkarmak için geçici bir dizi bağlamak fikrinizdir ve kodumun derlendiğini bile kontrol etmedim. Bence bu hala sizin çözümünüzdür ve biraz arıtma ile cevap verin;) Biri, Puppy'nin make_arraycevabındaki gibi bir varyasyona faydası olmadığını iddia edebilir .
dyp

Sağ. Ayrıca, şablonlar, sorunun gereksinimlerinden biri olan (iç içe hazırlanmış başlatma), başlatıcı listelerinden türleri çıkaramaz.
Gabriel Garcia

1

С ++ 17 kompakt uygulama.

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


0

Bir dizi oluşturucu türü oluşturun.

operator,Her bir öğeyi referanslar aracılığıyla bir öncekine zincirleyen bir ifade şablonu oluşturmak için aşırı yüklenir.

finishDizi yapıcıyı alan ve doğrudan başvuru zincirinden bir dizi oluşturan ücretsiz bir işlev ekleyin .

Sözdizimi şöyle görünmelidir:

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

Sadece olduğu {}gibi temelli yapıya izin operator=vermez. Kullanmaya istekliyseniz =işe alabiliriz:

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

veya

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

Bunların hiçbiri iyi çözümlere benzemiyor.

Variardics kullanmak, derleyici tarafından varar sayısı sınırını sınırlar ve {}alt yapıların yinelemeli kullanımını engeller .

Sonunda, gerçekten iyi bir çözüm yok.

Ne yapmam hem tüketir yüzden benim kod yazmak olduğunu T[]ve std::arrayverileri agnostically - bu onu beslemek hangi umursamıyor. Bazen bu, yönlendirme kodumun []dizileri dikkatlice std::arrayşeffaf bir şekilde s'ye dönüştürmesi gerektiği anlamına gelir .


1
"Bunlar iyi çözümlere benzemiyor." Ben de söyleyebilirim: p
kapaklar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.