2012-03-16 1 views
1

j'ai engagé dans un numéro dépendant du compilateur avec le code suivant (stocké dans crtp.cc):CRTP: question dépendant du compilateur avec expression modèle

#include <vector> 
#include <cassert> 
#include <iostream> 

template < class Derived > 
class AlgebraicVectorExpression { 
public: 
    typedef std::vector<double>::size_type SizeType; 
    typedef std::vector<double>::value_type ValueType; 
    typedef std::vector<double>::reference ReferenceType; 

    SizeType size() const { 
    return static_cast<const Derived&>(*this).size(); 
    } 

    ValueType operator[](SizeType ii) const { 
    return static_cast<const Derived&>(*this)[ii]; 
    } 

    operator Derived&() { 
    return static_cast<Derived&>(*this); 
    } 

    operator const Derived&() const { 
    return static_cast< const Derived& >(*this); 
    } 
}; 

template< class T1, class T2> 
class AlgebraicVectorSum : public AlgebraicVectorExpression< AlgebraicVectorSum<T1,T2> > { 
    const T1 & a_; 
    const T2 & b_; 

    typedef typename AlgebraicVectorExpression< AlgebraicVectorSum<T1,T2> >::SizeType SizeType; 
    typedef typename AlgebraicVectorExpression< AlgebraicVectorSum<T1,T2> >::ValueType ValueType; 
public: 

    AlgebraicVectorSum(const AlgebraicVectorExpression<T1>& a, const AlgebraicVectorExpression<T1>& b) : 
    a_(a), b_(b) { 
    assert(a_.size() == b_.size()); 
    } 

    SizeType size() const { 
    return a_.size(); 
    } 

    ValueType operator[](SizeType ii) const { 
    return (a_[ii] + b_[ii]); 
    } 

}; 

template< class T1, class T2> 
const AlgebraicVectorSum<T1,T2> 
operator+(const AlgebraicVectorExpression<T1>& a, const AlgebraicVectorExpression<T2>& b) { 
    return AlgebraicVectorSum<T1,T2>(a,b); 
} 

class AlgebraicVector : public AlgebraicVectorExpression<AlgebraicVector>{ 
    std::vector<double> data_; 

public: 
    SizeType size() const { 
    return data_.size(); 
    } 

    ValueType operator[](SizeType ii) const { 
    return data_[ii]; 
    } 

    ValueType& operator[](SizeType ii) { 
    return data_[ii]; 
    } 

    AlgebraicVector(SizeType n) : data_(n,0.0) { 
    }; 

    template< class T> 
    AlgebraicVector(const AlgebraicVectorExpression<T>& vec) { 
    const T& v = vec; 
    data_.resize(v.size()); 
    for(SizeType idx = 0; idx != v.size(); ++idx) { 
     data_[idx] = v[idx]; 
    } 
    } 
}; 

int main() { 

    AlgebraicVector x(10); 
    AlgebraicVector y(10); 
    for (int ii = 0; ii != 10; ++ii) 
    x[ii] = y[ii] = ii;  

    AlgebraicVector z(10); 
    z = x + y; 

    for(int ii = 0; ii != 10; ++ii) 
    std::cout << z[ii] << std::endl; 
    return 0; 
} 

En fait, quand je compile avec:

$ g++ --version 
g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3 
Copyright (C) 2009 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

$ g++ -O0 -g crtp.cc 

-je obtenir:

$ ./a.out 
0 
2 
4 
6 
8 
10 
12 
14 
16 
18 

qui est le comportement attendu. Quand j'utilise ICPC:

$ icpc --version 
icpc (ICC) 12.1.0 20110811 
Copyright (C) 1985-2011 Intel Corporation. All rights reserved.  
$ icpc -g -O0 crtp.cc 

Je place un Segmentation fault obtenir. Courir

valgrind --tool=memcheck ./a.out 

des points à la ligne 29 dans les sources

AlgebraicVectorExpression<AlgebraicVector>::operator AlgebraicVector const&() const (crtp.cc:29) 

Comme je suis tout à fait nouveau pour C++ et j'ai passé un certain temps à la recherche d'un bug sans résultat, je voudrais demander l'avis de quelqu'un plus expérimenté pour comprendre si ce problème est dû à une erreur que j'ai introduite (comme je l'espère) ou à un bug de compilateur.

Modifier: J'ai changé le code tel qu'il est maintenant, après la réponse de Mike Seymour. Maintenant, je ne reçois pas les avertissements du compilateur, mais j'ai toujours le même comportement que précédemment (avec la même réponse de valgrind). Est-ce que quelqu'un a essayé de compiler avec Intel?

Éditer: J'ai essayé de compiler le code dans la page Expression Templates de Wikipedia. J'ai obtenu le même comportement exact qu'avec l'exemple que j'ai fourni.

Modifier: J'ai étudié la question et il semble que la compilation avec Intel icpc l'opérateur

operator const Derived&() const { 
    return static_cast< const Derived& >(*this); 
    } 

appelle récursivement. Une solution que j'ai trouvé est de remplacer cet opérateur avec une méthode:

const Derived& get_ref() const { 
    return static_cast< const Derived& >(*this); 
    } 

et modifier les constructeurs des différentes classes en conséquence. Quelqu'un peut-il dire lequel de ces deux comportements est correct, en indiquant éventuellement la norme pour l'expliquer?

Répondre

6

Vous devez toujours activer les avertissements du compilateur; ils peuvent souvent détecter des problèmes subtils. Dans ce cas:

g++ -Wall -Wextra test.cpp 
test.cpp: In member function ‘const typename AlgebraicVectorExpression<AlgebraicVectorSum<T1, T2> >::ValueType& AlgebraicVectorSum<T1, T2>::operator[](typename AlgebraicVectorExpression<AlgebraicVectorSum<T1, T2> >::SizeType) const [with T1 = AlgebraicVector, T2 = AlgebraicVector]’: 
test.cpp:90: instantiated from ‘AlgebraicVector::AlgebraicVector(const AlgebraicVectorExpression<T1>&) [with T = AlgebraicVectorSum<AlgebraicVector, AlgebraicVector>]’ 
test.cpp:103: instantiated from here 
test.cpp:52: warning: returning reference to temporary 

Cela vous indique le problème:

const ValueType& operator[](SizeType ii) const { 
    return (a_[ii] + b_[ii]); 
} 

Le résultat de l'expression est temporaire, détruite à la fin de cette ligne, de sorte que la fonction renvoie une référence boiteuse à un objet inexistant. Cet opérateur devra retourner à la place par valeur, et vous ne devriez pas implémenter la surcharge non const puisqu'il n'y a aucune valeur à modifier.

+0

+1 vient de le découvrir aussi. – hmjd

+0

L'opérateur non-const [] 'subit également le même problème, ne doit pas être instancié car je n'ai reçu aucun avertissement pour cela. – hmjd

+0

@hmjd: Vous ne pouvez pas implémenter raisonnablement le non-const pour 'AlgebraicVectorSum' de toute façon. La version const devrait juste renvoyer une valeur au lieu d'une référence et tout ira bien. Je suis également curieux de savoir ce que vous gagnez en ayant 'AlgebraicVectorExpression' et en utilisant CRTP du tout dans ce cas. – Omnifarious