2015-04-29 4 views
0

Dans l'espoir de rendre mon code Fortran plus facile à transmettre en C++ un jour, j'ai travaillé sur un code de modèle d'expression pour fournir des opérateurs arithmétiques de tableau entier et la possibilité de copier et assigner . Malheureusement, je n'arrive pas à trouver un moyen de répondre à ma question, sans un peu de code standard que je réduirai autant que possible. Tout d'abord, j'ai une très simple structure de tableau de type C encapsulant un pointeur et une longueur, ce qui me permet de passer facilement des parties C, Fortran, C++ et Java de mon application en langage mixte :Affectation à une sous-section tableau: est-ce que j'attribue un Rvalue ici, et si oui, comment le réparer?

typedef struct { 
    int *p; /*!< Pointer to the data */ 
    int n;  /*!< The number of elements; int, not size_t, for Fortran compatibility */ 
} int_array_C; 

typedef struct { 
    float *p; /*!< Pointer to the data */ 
    int n;  /*!< The number of elements; int, not size_t, for Fortran compatibility */ 
} float_array_C; 

typedef struct { 
    double *p; /*!< Pointer to the data */ 
    int n;  /*!< The number of elements; int, not size_t, for Fortran compatibility */ 
} double_array_C; 

... et ainsi de suite pour tous les types natifs. Je définis alors certains modèles d'expression très simples basés sur l'approche proposée dans le Wikipedia entry on that subject:

template <typename E, typename T_C > 
class VecExpression 
{ 
    typedef typename std::remove_pointer<decltype(T_C::p)>::type TT; 
public: 
    //! Returns a const reference to the i'th element in the array 
    TT operator[] (int i) const noexcept 
    { 
     return static_cast<E const&>(*this)[i]; 
    } 

    //! Returns the total size of the array 
    int size() const noexcept 
    { 
     return static_cast<E const &>(*this).size(); 
    } 

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

template <typename E1, typename T_C, typename E2, typename U_C > 
class VecSum : public VecExpression< VecSum<E1, T_C, E2, U_C>, T_C > 
{ 
    E1 const & _u; 
    E2 const & _v; 
public: 
    //! Constructor taking two VecExpressions 
    VecSum(VecExpression<E1, T_C> const& u, VecExpression<E2, U_C> const &v) : _u(u), _v(v) 
    { 
     assert(u.size() == v.size()); 
    } 

    int size() const noexcept { return _v.size(); } 

    auto operator[](int i) const 
     -> const decltype(_u[i] + _v[i]) { return _u[i] + _v[i]; } 
       // Automatically takes care of type promotion e.g. int to double 
       // according to the compiler's normal rules 
}; 

template <typename E1, typename T_C, typename E2, typename U_C > 
VecSum<E1, T_C, E2, U_C> const operator+(VecExpression<E1, T_C> const &u, 
             VecExpression<E2, U_C> const &v) 
{ 
    return VecSum<E1, T_C, E2, U_C>(u, v); 
} 

Pour me donner une façon de manipuler le contenu de mes vecteurs de type C, je définis des modèles: celui qui manipule des données dans un tampon préexistant, et un autre qui gère sa propre mémoire en utilisant un vecteur std :::

template <typename T_C> class nArray : public T_C, public VecExpression<nArray <T_C>, T_C > 
{             // This is the 'curiously recurring template 
                // pattern' (CRTP) 
    typedef typename std::remove_pointer<decltype(T_C::p)>::type TT; 

    struct startingIndex : public T_C 
    { 
     size_t start; 

     startingIndex(const T_C *initialiser) noexcept 
     { 
      *(static_cast<T_C *>(this)) = *initialiser; 
     } 

     nArray to(int element) noexcept 
     { 
      T_C::n = element - start + 1; 
      nArray<T_C> newArray(*(static_cast<T_C *>(this))); 
      return newArray; 
     } 
    }; 

public: 
    //! Constructor to create an nArray from an array_C, without copying its memory 
    nArray(T_C theArray) noexcept 
    { 
     T_C::p = theArray.p; 
     T_C::n = theArray.n; 
    } 

    //! Constructor to create an nArray from an ordinary C array, without copying its memory 
    template<std::size_t N> 
    nArray(TT (&theArray)[N]) noexcept 
    { 
     T_C::p = &theArray[0]; 
     T_C::n = N; 
    } 

    nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) & 
    { 
     // Note that we cannot use the copy-and-swap idiom here because we don't have the means to 
     // construct a new temporary memory buffer. Therefore we have to handle the assignment-to-self 
     // case explicitly. 
     if (&source == this) return *this; 
     assert(T_C::n == source.size()); 
     for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i]; 
     return *this; 
    } 

    //! Copy assignment operator taking a VecExpression of a different (but compatible) type 
    //! without allocating any new memory 
    template <typename E, typename U_C> 
    nArray operator=(VecExpression<E, U_C> const& source) & 
    { 
     assert(T_C::n == source.size()); 
     for (int i=0; i<T_C::n; ++i) T_C::p[i] = static_cast<TT>(source[i]); 
     return *this; 
    } 

    //! Returns a non-const reference to the i'th element in the array 
    TT& operator[] (int i) noexcept 
    { 
     return T_C::p[i]; 
    } 

    //! Returns a const reference to the i'th element in the array 
    const TT& operator[] (int i) const noexcept 
    { 
     return T_C::p[i]; 
    } 

    startingIndex from(int element) const noexcept 
    { 
     startingIndex theNewArray(this); 
     theNewArray.p = &T_C::p[static_cast<size_t>(element)]; 
     theNewArray.n = T_C::n - element; 
     theNewArray.start = element; 
     return theNewArray; 
    } 

    nArray to(int element) const noexcept 
    { 
     nArray theNewArray; 
     theNewArray.p = T_C::p; 
     theNewArray.n = element + 1; 
     return theNewArray; 
    } 

    // ... and a whole bunch of other functions 
}; 

template <typename T_C> class nVector : public nArray<T_C> 
{ 
    typedef typename std::remove_pointer<decltype(T_C::p)>::type TT; 

public: 
    template<std::size_t N> 
    nVector(TT (&source)[N]) 
    { 
     contents.resize(N); 
     update_basetype(); 
     std::copy(&source[0], &source[N], contents.begin()); 
    } 

    // ...and a whole bunch of other constructors and assignment operators 
    // which echo those of nArray with the additional step of resizing the 
    // internal std::vector and copying the contents into it 

private: 
    void update_basetype() noexcept 
    { 
     T_C::p = contents.size() > 0 ? contents.data() : nullptr; 
     T_C::n = contents.size(); 
    } 

    std::vector<TT> contents; 
}; 

typedef nArray<float_array_C> float_array; 
typedef nVector<float_array_C> float_vector; 

// ...and so on 

Ouf! De ce point, je peux faire des choses comme

float a[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f }; 
float b[] = { 9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f }; 

float_array aArray(a); // The array contents aren't copied, only 
float_array bArray(b); // the pointers 

float_vector aVector = aArray.from(2); // aVector is { 3.0f, 4.0f, 5.0f, 6.0f } 
float_vector bVector = bArray.to(3); // bVector is { 9.0f, 8.0f, 7.0f, 6.0f } 
float_vector cVector = aArray.from(2).to(4) + bArray.from(1).to(3); 
             // cVector is { 11.0f, 11.0f, 11.0f } 

... et ils travaillent un régal. Maintenant, enfin, je peux venir à ma question. Supposons que je veux attribuer à une sous-section de tableau, par exemple:

float_vector dVector(10); // An empty 10-element array 
dVector.from(3).to(5) = aArray.from(2).to(4) + bArray.from(1).to(3); 

En fait, si je compile dans Visual C++ 2013 cela fonctionne très bien, mais en gcc il ne fonctionne pas. Compilation échoue à l'affectation, avec le message:

error: no match for 'operator=' (operand types are 'nArray<float_array_C>' and 'const VecSum<nArray<float_array_C>, float_array_C, nArray<float_array_C>, float_array_C>') 
note: candidates are: 
    < ...skipping over a long list of utterly implausible options> 
note: nArray<T_C>& nArray<T_C>::operator=(const VecExpression<nArray<T_C>, T_C>&) & [with T_C = float_array_C] 
note: no known conversion for implicit 'this' parameter form 'nArray<float_array_C>' to 'nArray<float_array_C>&' 

Maintenant, ce message d'erreur semble surgir sur la littérature en essayant d'assigner un objet temporaire à une référence non-const, ou en essayant de faire une mission à un Rvalue, et Visual C++ est documenté comme étant flou par rapport à cette règle que gcc est (avec gcc étant celui qui se conforme à la norme, naturellement). Je peux de type de comprendre pourquoi le compilateur peut considérer

dVector.from(3).to(5) 

comme rvalue, même si je suis penché en arrière pour essayer de l'empêcher d'être. Par exemple ma méthode startingIndex :: à() retourne un objet avec diligence nArray en valeur, non par référence, et si j'écris

auto test1 = dVector.from(3).to(5); 
auto test2 = aArray.from(2).to(4) + bArray.from(1).to(3); 
test1 = test2; 

... alors cela fonctionne très bien et le compilateur me dit que « test1 » est un 'nArray <float_array_C>' (c'est-à-dire un float_array) exactement comme il se doit. Donc, ma question est la suivante: est-ce que je suis coupable d'essayer d'assigner un Rvalue ici? Et si je le suis, comment puis-je cesser de le faire, tout en étant capable de faire des assignations à des sous-matrices de cette manière, ou du moins d'une manière lisible de la même manière. Je l'espère vraiment que cela peut être fait d'une certaine façon en C++, sinon je suppose que je vais devoir revenir à Fortran terre, écriture

dVector(3:5) = aArray(2:4) + bArray(1:3) 

... et vivre heureux pour toujours.

Mise à jour

Suite à la suggestion de Chris Dodd J'ai expérimenté avec deux formes différentes pour un autre constructeur nArray et se sont installés sur:

nArray && operator=(VecExpression<nArray<T_C>, T_C> const& source) && 
{ 
    if (&source == this) return *this; 
    assert(T_C::n == source.size()); 
    for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i]; 
    return *this; 
} 

(en laissant la nécessité de consolider l'affectation à -Vérifier pour le moment). Le compilateur gcc semblait avoir passé, mais l'erreur suivante je suis arrivé était:

no known conversion for argument 1 from 'const VecSum<nArray<float_array_C>, float_array_C, nArray<float_array_C>, float_array_C>' to 'const VecExpression<nArray<float_array_C>, float_array_C>&' 

Ceci est au moins un message différent (toujours un signe de progrès), mais il implique encore que le problème de base est une affectation à le mauvais type de référence. Malheureusement je ne suis pas sûr où je devrais chercher ceci: J'ai expérimenté avec faire mon opérateur + renvoyer un VecSum < E1, T_C, E2, U_C > const & plutôt qu'un retour par valeur, mais ceci n'a fait aucune différence précise . Pour l'instant je suis bloqué à nouveau, donc ma prochaine stratégie est d'installer clang dans ma partition Linux et voir si je reçois un message d'erreur plus utile de lui ...

mise à jour plus:

Clang wasn t particulièrement utile; tout ce qu'il pouvait dire était:

candidate function not viable: no known conversion from 'nArray<[...]>' to 'nArray<[...]>' for object argument 

qui ne donne pas beaucoup d'indices!

mise à jour finale:

Je suis en fait assez gêné de évident, rétrospectivement, la solution se révèle être. Tout ce que je devais faire était de donner mon opérateur d'affectation exactement la même forme que l'opérateur d'affectation de déplacement ordinaire aurait:

nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) && 
{ 
    // Better assignment-to-self check pending... 
    assert(T_C::n == source.size()); 
    for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i]; 
    return *this; 
} 

Bien sûr, est textuellement ce que Chris Dodd a suggéré en premier lieu, et fonctionne tout à fait bien dans clang et gcc sur Linux et Windows.

Répondre

2

Votre opérateur d'affectation nArray:

nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) & 

est explicitement défini comme désignant seulement lValue nArray objets (le & final sur cette ligne), et non à rvalue objets, ne peut donc pas être utilisé pour attribuer à tranches temporaires telles que celles que vous obtenez de dVector.from(3).to(5). Vous avez besoin d'un opérateur d'affectation avec une référence à rvalue « ce »:

nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) && 

afin de pouvoir appeler cet opérateur d'affectation sur une tranche temporaire comme celui-ci.

Notez que votre contrôle &source == this pour l'auto-affectation n'est pas adéquat. Vous pouvez avoir des objets nArray distincts qui font référence au même stockage sous-jacent, avec des découpages différents. Considérez ce qui se passe si vous essayez quelque chose comme

aVector.from(3).to(7) = aVector.from(1).to(5) 
+0

Ceci est un indice utile mais je suppose que je dois passer du temps à comprendre comment implémenter un tel opérateur. Ma première tentative naïve, en changeant le "nArray &" en "nArray &&" et en enveloppant le '* this' retourné dans un 'std :: move' a échoué. –

+0

Pas besoin de 'std :: move' - utiliser' && 'ne fait pas de ceci une valeur, cela permet juste d'appeler la méthode sur rvalues. Voir http://stackoverflow.com/questions/8610571/what-is-rvalue-reference-for-this –

+0

J'ai essayé quelques variantes supplémentaires sur la syntaxe et je n'ai toujours pas réussi. N'effectuer aucune modification à l'exception d'un autre opérateur d'affectation qui est identique en dehors d'un qualificatif '&&' ne fonctionne pas de la même manière. Je suppose que je dois dormir dessus et réessayer demain. Je vous remercie de l'allusion au chèque d'auto-affectation, d'ailleurs. –