OVH Cloud OVH Cloud

dimensions de tableaux

19 réponses
Avatar
Nicolas Bonneel
Bonjour,

Je souhaites realiser la gestion de matrices multidimensionnelles dans mon
programme.
J'ai créé un classe TMatrix, qui comporte le constructeur :

TMatrix::TMatrix(int Shape[], double Valeurs[])
{
dim = sizeof(Shape)/sizeof(Shape[0]);
nbelements = sizeof(Valeurs)/sizeof(Valeurs[0]);

[......]
}

Alors se posent plusieurs questions :
1- Je ne peux pas créer de matrices en faisant directement : TMatrix* mat =
new TMatrix({2,2},{1.1 0.5 0.7 0.6});
, je suis obligé de créer des variables int[] et double[] et les passer
en parametre avec ces valeurs.... Comment puis-je y remedier ?

2- En passant un Shape de : int[] Shape = {2,2}; lorsque j'execute le
programme, il me met dans dim la valeur 1 (et quel que soit le vecteur Shape
d'ailleurs...). Comment cela se fait-il ? Comment y remedier ?!


Merci beaucoup !

Nicolas Bonneel
http://www.byosphere.com

9 réponses

1 2
Avatar
kanze
drkm wrote:
writes:

(Il y a une solution encore plus élégant, avec un Shape<N> :
la class Shape<N> contient un Shape<N-1> et un int, et il y
a spécialisation pour Shape<1>. Mais j'avoue que l'écrire
dépasse de loin mes compétences en templates.)


Mais il serait alors forcé de connaître le nombre d'élément
d'un Shape à la compilation. C'est pas un peu contraignant ?


Ne connaissant pas son application, je ne peux pas dire. C'est
une solution que j'ai cité « pour référence » ; n'étant pas
assez fort en C++ pour l'implémenter ni le comprendre, je ne
risque pas moi-même de l'utiliser, et n'ayant jamais utiliser,
je ne peux pas trop parler des pour et des contre.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
xavier
a dis le 10/01/2005 16:01:
n'étant pas assez fort en C++ pour l'implémenter ni le comprendre


En fait, l'implémentation est plutôt simple :
// test.cpp
#include <cassert>

template <int N>
class Foo {
public:

enum { element_count = N };

int & operator[](int index) {
assert (index >= 0 && index < element_count);
if(index == element_count - 1)
return element;
return sub[index];
}

int const & operator[](int index) const {
assert (index >= 0 && index < element_count);
if(index == element_count - 1)
return element;
return sub[index];
}

private:
Foo<N - 1> sub;
int element;
};

template <>
class Foo<0> {
public:
int & operator[](int index) {
assert (index == 0);
return element;
}

int const & operator[](int index) const {
assert (index == 0);
return element;
}

private:
int element;
};

#include <iostream>

template <int N>
std::ostream & operator<<(std::ostream & o, Foo<N> const & f) {
o << "Foo(";
int i = 0;
for(; i < N -1; ++i)
o << f[i] << ", ";
o << f[N - 1] << ")";

return o;
}

int main() {
const int test_count = 5;

Foo<test_count> f;
for(int i = 0; i < test_count; ++i) {
f[i] = i + 1;
}
std::cout << f << std::endl;
}
//eof

$ g++ -o mtest test_tmpl.cpp && ./mtest
Foo(1, 2, 3, 4, 5)

Avatar
drkm
xavier writes:

a dis le 10/01/2005 16:01:

n'étant pas assez fort en C++ pour l'implémenter ni le comprendre


En fait, l'implémentation est plutôt simple :


[...]

template <>
class Foo<0> {
^^^


template <>
class Foo< 1 > {

Non ?

Et puis l'opérateur [] est en O(n). C'est pas vraiment ce que
j'appelle élégant. Si je me souviens bien (ça fait longtemps), Loki
contient une classe tuple, généralisant ce genre de vecteur à des
tuples dont les éléments peuvent être de types différents. Et accès
en O(1), par héritage.

Je ne me souviens plus des détails, mais ça devrait être une bonne
source d'inspiration.

--drkm


Avatar
James Kanze
xavier wrote:
a dis le 10/01/2005 16:01:


n'étant pas assez fort en C++ pour l'implémenter ni le comprendre



En fait, l'implémentation est plutôt simple :


Il manque les constructeurs dans ton exemple. C-à-d la partie
qui nous intéresse le plus -- le seul but ici, c'est d'avoir des
constructeurs intuitifs et facile à utiliser.

Et c'est ça que je ne sait pas faire. Faire en sort que le
constructeur pour Shape<N> prend N paramètres, se sert du
dernier, et passe les autres à Shape<N-1>.

--
James Kanze home: www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
Olivier Azeau
James Kanze wrote:
xavier wrote:
a dis le 10/01/2005 16:01:

n'étant pas assez fort en C++ pour l'implémenter ni le comprendre


En fait, l'implémentation est plutôt simple :


Il manque les constructeurs dans ton exemple. C-à-d la partie
qui nous intéresse le plus -- le seul but ici, c'est d'avoir des
constructeurs intuitifs et facile à utiliser.

Et c'est ça que je ne sait pas faire. Faire en sort que le
constructeur pour Shape<N> prend N paramètres, se sert du
dernier, et passe les autres à Shape<N-1>.



J'ai du mal à saisir le fond du problème.
Si les valeurs sont constantes, on peut bien passer un "tableau C" au
constructeur, non ?

template <int N>
class Foo {
Foo( const int *array ) : sub(array), element(array[N-1]) {}
//....
template <>
class Foo<1> {
Foo( const int *array ) : element(array[0]) {}
//....
int main() {
int x[] = {1,2,3,4,5};
Foo<5> f(x);
std::cout << f << std::endl;
}

Si les valeurs sont variables, j'aime bien la technique suivante :
On définit un opérateur "<<" entre l'objet container et l'objet élément
(ici Foo et int) et on se sert d'un objet container dummy pour initier
la liste.
Je n'ai jamais évalué le coût de construction des objets temporaires,
mais en général, pour des petites listes, cela ne se remarque pas...

template <int N>
class Foo {
Foo(const Foo<N-1> &f, int i) : sub(f), element(i) {}
//...
template <>
class Foo<1> {
Foo(int i) : element(i) {}
//...

template <>
class Foo<0> {
public: Foo() {}
};

const Foo<0> foos;

template <int N>
Foo<N+1> operator<<( const Foo<N> &f, int i)
{
return Foo<N+1>(f,i);
}

template <>
Foo<1> operator<<( const Foo<0> &f, int i)
{
return Foo<1>(i);
}

int main() {
Foo<5> f( foos << 1 << 2 << 3 << 4 << 5 );
std::cout << f << std::endl;
}



Avatar
drkm
Olivier Azeau writes:

J'ai du mal à saisir le fond du problème.
Si les valeurs sont constantes, on peut bien passer un "tableau C" au
constructeur, non ?


Je pense que le PO veut justement éviter la construction d'un tel
tableau juste pour le passer en argument. Il souhaitait quelque chose
comme :

func( { 0 , 1 , 2 } ) ;

Et James propose des Shape<> dont les constructeurs sont :

Shape<1>::Shape<1>( T arg1 ) ;
Shape<2>::Shape<2>( T arg1 , T arg2 ) ;
...
Shape<N>::Shape<N>( T arg1 , ... , T argN ) ;

--drkm

Avatar
drkm
Olivier Azeau writes:

drkm wrote:

Et James propose des Shape<> dont les constructeurs sont :
Shape<1>::Shape<1>( T arg1 ) ;
Shape<2>::Shape<2>( T arg1 , T arg2 ) ;
...
Shape<N>::Shape<N>( T arg1 , ... , T argN ) ;


Donc ça correspond à ma 2ème proposition ?

Shape<5> myShape( shaper << arg1 << arg2 << arg3 << arg4 << arg5 );


A priori, oui. Je n'ai pas le temps de regarder très attentivement.
J'étais passé un peu vite dessus, désolé.

D'un autre côté, je n'aime pas trop le détournement des opérateurs.
Surtout si l'on peut faire autrement. Bien que dans ce cas précis, ça
ne me gênerait pas.

--drkm


Avatar
kanze
Olivier Azeau wrote:
James Kanze wrote:
xavier wrote:
a dis le 10/01/2005 16:01:

n'étant pas assez fort en C++ pour l'implémenter ni le
comprendre





En fait, l'implémentation est plutôt simple :


Il manque les constructeurs dans ton exemple. C-à-d la
partie qui nous intéresse le plus -- le seul but ici, c'est
d'avoir des constructeurs intuitifs et facile à utiliser.

Et c'est ça que je ne sait pas faire. Faire en sort que le
constructeur pour Shape<N> prend N paramètres, se sert du
dernier, et passe les autres à Shape<N-1>.


J'ai du mal à saisir le fond du problème.

Si les valeurs sont constantes, on peut bien passer un
"tableau C" au constructeur, non ?


Le but, c'est justement d'éviter à ce que l'utilisateur ait à
déclarer un tableau ailleurs. On veut qu'il puisse donner les
valeurs directement dans le constructeur. Qu'elles soient
constantes ou non, parce que je crois que le « constante » ne
change pas grand chose dans l'affaire, mais que le nombre de
valeurs soit connu au moment de la compilation.

Si les valeurs sont variables, j'aime bien la technique
suivante :

On définit un opérateur "<<" entre l'objet container et
l'objet élément (ici Foo et int) et on se sert d'un objet
container dummy pour initier la liste.


C'est une idée intéressante. Évidemment, l'opérateur << n'est
pas très indiqué, mais quelque chose comme :
Shape().dim( 3 ).dim( 3 ).dim( 5 )
me semble faisable. (J'aimerais bien aussi un opérateur, mais je
n'en vois pas qui ferait l'affaire.)

Je n'ai jamais évalué le coût de construction des objets
temporaires, mais en général, pour des petites listes, cela ne
se remarque pas...


Tout à fait.

template <int N>
class Foo {
Foo(const Foo<N-1> &f, int i) : sub(f), element(i) {}
//...
template <>
class Foo<1> {
Foo(int i) : element(i) {}
//...


Pourquoi un template, puisque N va être déterminé en fait de
façon dynamique ?

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34




Avatar
Olivier Azeau
wrote:
Olivier Azeau wrote:
template <int N>
class Foo {
Foo(const Foo<N-1> &f, int i) : sub(f), element(i) {}
//...
template <>
class Foo<1> {
Foo(int i) : element(i) {}
//...



Pourquoi un template, puisque N va être déterminé en fait de
façon dynamique ?


Je ne comprends pas ce que tu veux dire.
Je suis bien obligé de connaître N si je veux déclarer un Foo<N>, non ?


1 2