Forum: PC-Programmierung std::array of objects in initializer list


von Operator S. (smkr)


Lesenswert?

Kann man die Position eines Objektes, welches in einem std::array liegt 
seinem eigenen Konstruktor übergeben?

folgendes Problem:
1
//Actor.h
2
class Actor {
3
public:
4
    Actor(const std::string &moduleName, int baseAddress);
5
}
6
7
//Device.h
8
class Device{
9
public:
10
    Device();
11
12
private:
13
    std::array<Actor, 16> actorDigital;
14
}
15
16
//Device.cpp
17
Device::Device() :
18
  // std::array needs double {{..}} to initialize objects
19
  actorDigital{{   Actor("DigitalOut1"),
20
    Actor("DigitalOut2"),
21
    Actor("DigitalOut3"),
22
    Actor("DigitalOut4"),
23
    Actor("DigitalOut5"),
24
    Actor("DigitalOut6"),
25
    Actor("DigitalOut7"),
26
    Actor("DigitalOut8"),
27
    Actor("DigitalOut9"),
28
    Actor("DigitalOut10"),
29
    Actor("DigitalOut11"),
30
    Actor("DigitalOut12"),
31
    Actor("DigitalOut13"),
32
    Actor("DigitalOut14"),
33
    Actor("DigitalOut15"),
34
    Actor("DigitalOut16")
35
  }}
36
{
37
}

Zuerst hatte ich noch keine baseAddress im Actor() und habe von Hand die 
Namen vergeben, was mir eigentlich schon nicht gefiel. Nun müsste ich 
noch anhand des Index die baseAddress vergeben, dies wäre etwas in der 
Art:

address + (index * x)

Genauso könnte ich den Namen generieren lassen, ohne von Hand 
durchzuzählen, mein Ziel wäre:

Actor("DigitalOut" + to_string(index), address + (index * x) )

Gibt es einen Weg das std::array so zu initialisieren?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Wenn es nur um die Nummer geht -> statische Datenmember.
Aber das ist wahrscheinlich nicht das, was Du willst.

von Operator S. (smkr)


Lesenswert?

Kannst du mir mehr über die statischen Datenmember sagen? (Link zu 
beispiel oder so)

Würde ich stattdessen das array mit pointer füllen, hätte ich vermutlich 
so etwas gemacht:
1
for(unsigned int i=0; i<actorDigital.size(); ++i){
2
    actorDigital[i] = new Actor(("DigitalOut" + to_string(i), 0x100 + (i * 0x04) );
3
}

Bei dieser Lösung könnte ich aber gleich zum vector<> gehen und hätte 
das ganze sauberer gelöst.

(Das es hier im Beispiel bei 0 Anfangen würde und nicht bei 1 soll mal 
ausser acht gelassen werden, aber ich denke die Idee ist klar.)

von Oliver S. (oliverso)


Lesenswert?

Am einfachsten in solchen Fällen ist es doch, das einfach mal 
auszuprobieren (und schön wäre es, wenn dann im Ausgangsbeitrag schon 
compilierbarerer Code gepostet wird):
1
#include <array>
2
#include <iostream>
3
4
class Actor
5
{
6
public:
7
  const std::string Name;
8
  const int index;
9
    Actor(const std::string &moduleName, int baseAddress) : Name(moduleName),
10
                                index(baseAddress)
11
{}
12
};
13
14
class Device{
15
public:
16
    Device();
17
18
public:
19
    int index = 0;
20
    const int address = 1;
21
    std::array<Actor, 2> actorDigital;
22
};
23
24
//Device.cpp
25
Device::Device() :
26
  // std::array needs double {{..}} to initialize objects
27
  actorDigital{   Actor("DigitalOut" + std::to_string(index), index++),
28
                Actor("DigitalOut" + std::to_string(index), index++)
29
              }
30
{
31
}
32
33
int main()
34
{
35
  Device d;
36
  for (const auto& a : d.actorDigital)
37
  {
38
    std::cout << a.Name << " " << a.index << std::endl;
39
  }
40
}

DigitalOut1 0
DigitalOut2 1


Oliver

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Oliver S. schrieb:
> Am einfachsten in solchen Fällen ist es doch, das einfach mal
> auszuprobieren:

Das ist aber undefiniertes Verhalten , pures Glück dass es funktioniert. 
Wenn man eine Variable (hier Index) innerhalb eines Ausdrucks mehrfach 
verändert passiert etwas Compiler spezifisches...

von Wilhelm M. (wimalopaan)


Lesenswert?

Dachte etwa an:
1
#include <array>
2
#include <string>
3
#include <iostream>
4
5
class Actor
6
{
7
public:
8
    Actor(const std::string& baseName = "bla") : name(baseName + std::to_string(numberOfActors++)) {}
9
//private:
10
    static size_t numberOfActors;
11
    std::string name;
12
};
13
size_t Actor::numberOfActors = 0;
14
15
std::ostream& operator<<(std::ostream& o, const Actor& a) {
16
    return o << a.name;
17
}
18
19
int main()
20
{
21
    std::array<Actor, 3> actors;
22
    
23
    for(auto& a: actors) {
24
        std::cout << a << std::endl;
25
    }
26
        
27
}

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Oder so:
1
class Actor
2
{
3
public:
4
    friend std::ostream& operator<<(std::ostream& o, const Actor& a);
5
    Actor(const std::string& baseName = "bla") : name(baseName + std::to_string(numberOfActors)), 
6
        number(numberOfActors++) {}
7
private:
8
    static size_t numberOfActors;
9
    std::string name;
10
    const size_t number;
11
};
12
size_t Actor::numberOfActors = 0;
13
14
std::ostream& operator<<(std::ostream& o, const Actor& a) {
15
    return o << a.name;
16
}
17
18
int main()
19
{
20
    std::array<Actor, 3> actors;
21
    
22
    for(const auto& a: actors) {
23
        std::cout << a << std::endl;
24
    }
25
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Wobei das Generieren des Namens / Nummer sollte man ggf. in eine Factory 
refaktorieren ...

von Wilhelm M. (wimalopaan)


Lesenswert?

Operator S. schrieb:

> Bei dieser Lösung könnte ich aber gleich zum vector<> gehen und hätte
> das ganze sauberer gelöst.

Ja, warum nicht? Dann vllt auf die heap-allocation verzichten?

Die Frage ist: hast Du Allocator auf Deiner Plattform? Schaut aber so 
aus, als ob Du nicht auf BareMetal bist ...

von Operator S. (smkr)


Lesenswert?

Danke für Antworten, es scheint also keine direkte Lösung über 
std::array zu geben?

Beitrag "Re: std::array of objects in initializer list"

An dieser Lösung fehlt mir der boundary check, der ja mit array möglich 
wäre. Ebenso muss wieder viel geschrieben werden, wollte das eigentlich 
vermeiden und nur eine "vorlage" schreiben, die der Compiler richtig 
generiert.

Beitrag "Re: std::array of objects in initializer list"

Dieser Ansatz habe ich auch schon angedacht, aber wenn ich ein zweites 
array ala

std::array<Actor, 3> actorsDigital;
std::array<Actor, 3> actorsAnalog;

habe, funktioniert es wieder nicht. Der index müsste auf das array 
bezogen sein und nicht auf die Objekte.

std::array wollte ich einsetzen da ich eine feste Grösse habe, aber 
nicht auf Actor actor[16] setzen wollte, alleine schon wegen 
range-based-loops. Irgendwie bin ich aber nie zufrieden mit dem 
std::array, so dass ich doch raw-arrays oder std::vector einsetze - 
schade.


Bezüglich dem compilierbaren code: Wie so oft ist die eigentliche Klasse 
viel grösser und einige Namen mussten firmenbezogen obfuscated werden. 
Werde mir aber in Zukunft mehr mühe geben.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Operator S. schrieb:
> Actor("DigitalOut" + to_string(index), address + (index * x) )
>
> Gibt es einen Weg das std::array so zu initialisieren?

Fast! ;-) Es wäre "nur" ein Array und die Lösung ist etwas unleserlich:
1
#include <tuple>
2
#include <type_traits>
3
#include <iostream>
4
#include <array>
5
6
class actor
7
{
8
public:
9
    explicit actor( int i ) : id_( "DigitalOut" + std::to_string( i ) ) {}
10
11
    std::string id() const
12
    {
13
        return id_;
14
    }
15
16
private:
17
    const std::string id_;
18
};
19
20
template < typename T >
21
struct generate_actor;
22
23
template < int I >
24
struct generate_actor< std::integral_constant< int, I > >
25
{
26
    static const actor a;
27
};
28
29
template < int I >
30
const actor generate_actor< std::integral_constant< int, I > >::a = actor( I );
31
32
template < typename ...Ts >
33
struct unroll_actors;
34
35
template < typename ...Ts >
36
struct unroll_actors< std::tuple< Ts... > >
37
{
38
    static const actor actors[ sizeof ...(Ts) ];
39
};
40
41
template < typename ...Ts >
42
const actor unroll_actors< std::tuple< Ts... > >::actors[ sizeof ...(Ts) ] = { generate_actor< Ts >::a... };
43
44
template < typename A, typename B >
45
struct add;
46
47
template < typename ...As, typename ...Bs >
48
struct add< std::tuple< As... >, std::tuple< Bs... > >
49
{
50
    using type = std::tuple< As..., Bs... >;
51
};
52
53
template < int I >
54
struct generate_actor_args
55
{
56
    using type = typename add<
57
        typename generate_actor_args< I - 1 >::type,
58
        std::tuple< std::integral_constant< int, I > >
59
    >::type;
60
};
61
62
template <>
63
struct generate_actor_args< 1 >
64
{
65
    using type = std::tuple< std::integral_constant< int, 1 > >;
66
};
67
68
int main()
69
{
70
    const actor (&theater)[ 16 ] = unroll_actors< generate_actor_args< 16 >::type >::actors;
71
72
    for ( const auto a : unroll_actors< generate_actor_args< 16 >::type >::actors )
73
        std::cout << a.id() << std::endl;
74
}

von Wilhelm M. (wimalopaan)


Lesenswert?

So villt:
1
struct Digital {
2
    static constexpr const char name[] = "D";
3
};
4
struct Analog {
5
    static constexpr const char name[] = "A";
6
};
7
8
template<typename Type>
9
class Actor
10
{
11
public:
12
    Actor(const std::string& baseName = Type::name) : name(baseName + std::to_string(numberOfActors)), 
13
        number(numberOfActors++) {}
14
    static size_t numberOfActors;
15
    std::string name;
16
    const size_t number;
17
};
18
template<typename Type>
19
size_t Actor<Type>::numberOfActors = 0;
20
21
template<typename Type>
22
std::ostream& operator<<(std::ostream& o, const Actor<Type>& a) {
23
    return o << a.name;
24
}
25
26
int main()
27
{
28
    std::array<Actor<Digital>, 3> actors1;
29
    std::array<Actor<Analog>, 3> actors2;
30
    
31
    for(const auto& a: actors1) {
32
        std::cout << a << std::endl;
33
    }
34
    for(const auto& a: actors2) {
35
        std::cout << a << std::endl;
36
    }
37
}

von Oliver S. (oliverso)


Lesenswert?

Dr. Sommer schrieb:
> Das ist aber undefiniertes Verhalten , pures Glück dass es funktioniert.
> Wenn man eine Variable (hier Index) innerhalb eines Ausdrucks mehrfach
> verändert passiert etwas Compiler spezifisches...

Hm. Ich erkenne deine Kompetenz normalerweise neidlos an, aber...

... hätte da gerne etwas mehr Info.

Der Standard (C++11, Draft N3376) schreibt:
1
Within the initializer-list of a braced-init-list, the initializer-clauses, 
2
including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and
3
side effect associated with a given initializer-clause
4
is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the
5
initializer-list.

Oliver

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Nochwas:
1
template<typename Type>
2
class Actor2
3
{
4
public:
5
    Actor2(int id) : name(std::string{Type::name} + std::to_string(id)), 
6
        mId(id) {}
7
    std::string name;
8
    const size_t mId;
9
};
10
11
template<typename Type>
12
std::ostream& operator<<(std::ostream& o, const Actor2<Type>& a) {
13
    return o << a.name;
14
}
15
16
Actor2<Digital> operator"" _da(unsigned long long v) {
17
    return Actor2<Digital>(v);
18
}
19
20
Actor2<Analog> operator"" _aa(unsigned long long v) {
21
    return Actor2<Analog>(v);
22
}
23
24
int main()
25
{
26
    std::array<Actor2<Digital>, 3> actors3 = {{1_da, 3_da, 2_da}};
27
    std::array<Actor2<Analog>, 3> actors4 = {{1_aa, 3_aa, 2_aa}};
28
}

von Mikro 7. (mikro77)


Lesenswert?

Operator S. schrieb:
> Kann man die Position eines Objektes, welches in einem std::array liegt
> seinem eigenen Konstruktor übergeben?

Ich hatte mir mal ein make_array geschrieben, um std::array für 
bestimmte Fälle "dynamisch" initialisierbar zu machen (nicht exception 
safe). Dann könnte man in deinem Fall bspw. über einen std::vector gehen 
und folgerndermaßen verfahren:
1
#include <array>
2
#include <iostream>
3
#include <sstream>
4
#include <string>
5
#include <vector>
6
7
template <size_t N,typename Iterator>
8
static std::array<typename std::iterator_traits<Iterator>::value_type,N> make_array(Iterator first,Iterator last)
9
{
10
  using T = typename std::iterator_traits<Iterator>::value_type ;
11
  char buffer[sizeof(std::array<T,N>)] ;
12
  auto *p = reinterpret_cast<std::array<T,N>*>(&buffer) ;
13
  for (size_t i=0 ; i<N ; ++i) {
14
    if (first == last)  
15
      throw std::runtime_error("make_array:underflow") ;
16
    new (&(*p)[i]) T(*first) ;
17
    ++first ;
18
  }
19
  if (first != last) 
20
    throw std::runtime_error("make_array:overflow") ;
21
  auto array = (*p) ;
22
  for (size_t i=0 ; i<N ; ++i) {
23
    (*p)[i].~T() ;
24
  }
25
  return array ;
26
}
27
28
struct Actor
29
{
30
  std::string s ; int i ;
31
  Actor(std::string const &s, int i) : s(s),i(i) {}
32
} ;
33
34
struct Device
35
{
36
  static Device make()
37
  {
38
    std::vector<Actor> v ;
39
    for (int i=1 ; i<=16 ; ++i)
40
      v.push_back(Actor("DigitalOut"+std::to_string(i),i)) ;
41
    return Device(v) ;
42
  }
43
  
44
  Device(std::vector<Actor> const &v) : a(make_array<16>(v.begin(),v.end())) {}
45
  
46
  std::string to_str() const
47
  {
48
    std::ostringstream os ;
49
    for (auto &actor : a)
50
      os << actor.i << ':' << actor.s << '\n' ;
51
    return os.str() ;
52
  }
53
      
54
private:
55
  std::array<Actor,16> a ;
56
} ;
57
58
int main()
59
{
60
  auto d = Device::make() ;
61
  std::cout << d.to_str() ;
62
  return 0 ;
63
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Also ich finds so ganz hübsch:
1
#include <array>
2
#include <string>
3
#include <iostream>
4
5
struct Digital {
6
    static constexpr const char name[] = "Digital";
7
    static constexpr const uint16_t baseAddress = 0x100;
8
};
9
struct Analog {
10
    static constexpr const char name[] = "Aanalog";
11
    static constexpr const uint16_t baseAddress = 0x200;
12
};
13
14
template<typename Type> class Actor;
15
template<typename Type> std::ostream& operator<<(std::ostream& o, const Actor<Type>& a);
16
17
template<typename Type>
18
class Actor
19
{
20
    friend std::ostream& operator<< <>(std::ostream& o, const Actor<Type>& a);
21
public:
22
    Actor(int id) : name(std::string{Type::name} + std::to_string(id)), mId(id), address(Type::baseAddress + mId) {}
23
private:
24
    std::string name;
25
    const size_t mId;
26
    const uint16_t address;
27
};
28
29
template<typename Type>
30
std::ostream& operator<<(std::ostream& o, const Actor<Type>& a) {
31
    return o << a.name << " : " << a.address;
32
}
33
34
Actor<Digital> operator"" _da(unsigned long long v) {
35
    return Actor<Digital>(v);
36
}
37
38
Actor<Analog> operator"" _aa(unsigned long long v) {
39
    return Actor<Analog>(v);
40
}
41
42
int main()
43
{
44
    std::array<Actor<Digital>, 3> actors1 = {{1_da, 3_da, 2_da}};
45
    std::array<Actor<Analog>, 3>  actors2 = {{1_aa, 3_aa, 2_aa}};
46
    
47
    for(const auto& a: actors1) {
48
        std::cout << a << std::endl;
49
    }
50
    for(const auto& a: actors2) {
51
        std::cout << a << std::endl;
52
    }
53
}

vielleicht triffts ja den Geschmack...

von Operator S. (smkr)


Lesenswert?

Danke für all die Lösungen. Ich hatte gehofft irgendwie std::fill oder 
std::generate einsetzen zu können, um näher am standard zu bleiben und 
weniger eigenen Code zu produzieren, aber das scheint in diesem Fall 
nicht möglich zu sein.
Auch sonst im Netz scheints immer auf template magic hinauszulaufen :)

von Wilhelm M. (wimalopaan)


Lesenswert?

Sag das doch gleich:
1
    int startId = 0;
2
    std::generate(std::begin(actors1), std::end(actors1), [&](){return Actor<Digital>(startId++);});

von Wilhelm M. (wimalopaan)


Lesenswert?

Oder:
1
    std::vector<Actor<Digital>> v;
2
    int startId = 0;
3
    std::generate_n(std::back_inserter(v), 10, [&](){return Actor<Digital>(startId++);});

Allerdings sprachst Du oben von Initialisierung.

Was wir hier geschieht ist ja eine Kopier/Verschiebungszuweisung ...

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.