2016-04-24 1 views
5

J'essaye de construire un constructeur pour prendre un tableau comme un argument qui surcharge un autre qui prend un scalaire à la place. Le code est ci-dessous.Constructeur de gabarit de classe C++ - référence de surcharge (U &) avec tableau (U *) échoué

#include <iostream> 

template <typename T> 
class SmallVec { // This is a 3 dimensional vector class template 
public: 
    T data[3] = {0}; // internal data of class 
    template <typename U> 
    explicit SmallVec(const U& scalar) { // if a scalar, copy it to each element in data 
     for(auto &item : data) { 
      item = static_cast<T>(scalar); 
     } 
    } 
    template <typename U> 
    explicit SmallVec(const U* vec) { // if a vector, copy one by one 
     for(auto &item : data) { 
      item = static_cast<T>(*vec); 
      vec++; 
     } 
    } 
}; 

int main() { 
    float num = 1.2; 
    float *arr = new float[3]; 
    arr[2] = 3.4; 
    SmallVec<float> vec1(num); // take num, which works fine 
    SmallVec<float> vec2(arr); // !!!--- error happens this line ---!!! 
    std::cout << vec1.data[2] << " " << vec2.data[2] << std::endl; 
    return 0; 
} 

Le compilateur se plaint que

error: invalid static_cast from type 'float* const' to type 'float' 

De toute évidence, vec2(arr) appelle toujours le premier constructeur. Toutefois, si j'enlève template <typename U> et remplace U par T. Le programme fonctionne bien. Que dois-je faire pour corriger cela?

Toutes les suggestions sont appréciées!

+0

Indice: Le premier constructeur est appelé deux fois. – LogicStuff

+0

@LogicStuff Merci beaucoup pour le commentaire rapide. Oui, le premier constructeur est appelé à nouveau. Cela signifie probablement tableau (ou pointeur vers tableau) est passé par référence? Mais comment dois-je dire au programme de trouver le second constructeur, ou comment devrais-je distinguer si l'argument est scalaire ou tableau dans la première méthode? Voulez-vous me donner quelques indices s'il vous plaît? – astroboylrx

+0

Vous devrez utiliser [SFINAE] (http://en.cppreference.com/w/cpp/language/sfinae) ou l'envoi de tags. – LogicStuff

Répondre

2

J'essaye de construire un constructeur pour prendre un tableau comme argument

(...)

explicit SmallVec(const U* vec) { // if a vector, copy one by one 

Vous ne prenez pas un tableau. Vous prenez un pointeur, qui peut ou non pointer vers un tableau, et même si elle pointe vers un tableau, qui dit que le tableau a au moins trois éléments? C'est un défaut de conception sérieux.

C++ ne vous permet de prendre des tableaux bruts par référence ou référence const, même si la syntaxe est horrible:

explicit SmallVec(const U (&vec)[3]) { 

La mise en œuvre du constructeur est alors aussi différent:

for(int index = 0; index < 3; ++index) { 
     data[index] = static_cast<T>(vec[index]); 
    } 

Recherche à main, cependant, le problème va plus loin. Vous utilisez new[] pour allouer dynamiquement un tableau. C'est déjà une très mauvaise idée.Par coïncidence, votre exemple manque également un delete[]. Pourquoi n'utilisez-vous pas un tableau local à la place?

float arr[3]; 

Cela rendra votre programme compiler et probablement exécuter correctement, mais il y a un comportement encore mal défini dans votre code, parce que vous définissez seulement le 3ème élément du tableau à une valeur valide; les deux autres éléments restent non initialisés et la lecture d'un float non initialisé, même si vous ne faites que le copier, entraîne formellement un comportement indéfini.

donc mieux le rendre:

float arr[3] = { 0.0, 0.0, 3.4 }; 

En plus de cela, 11 C++ vous invite à utiliser std::array, ce qui rend généralement les choses un peu plus sûr et améliore la syntaxe. Voici un exemple complet:

#include <iostream> 
#include <array> 

template <typename T> 
class SmallVec { // This is a 3 dimensional vector class template 
public: 
    std::array<T, 3> data; // internal data of class 
    template <typename U> 
    explicit SmallVec(const U& scalar) { // if a scalar, copy it to each element in data 
     for(auto &item : data) { 
      item = static_cast<T>(scalar); 
     } 
    } 
    template <typename U> 
    explicit SmallVec(std::array<U, 3> const& vec) { // if a vector, copy one by one 
     for(int index = 0; index < 3; ++index) { 
      data[index] = static_cast<T>(vec[index]); 
     } 
    } 
}; 

int main() { 
    float num = 1.2; 
    std::array<float, 3> arr = { 0.0, 0.0, 3.4 }; 
    SmallVec<float> vec1(num); 
    SmallVec<float> vec2(arr); 
    std::cout << vec1.data[2] << " " << vec2.data[2] << std::endl; 
    return 0; 
} 
+0

Merci pour cette explication détaillée! C'est utile. Oui, c'était de ma faute. J'ai d'abord utilisé 'arr [3]', alors j'ai pensé que je devrais donner un pointeur au constructeur, donc je suis passé à 'new []' mais j'ai oublié 'delete []'. Maintenant, je comprends que j'avais tort de passer un pointeur sur un tableau. Oui, les variables définies à l'intérieur des fonctions ne seront pas initialisées et je devrais éviter ces comportements non définis. J'apprécie que vous le signaliez. :-) Je ferai attention quand je poserai des questions plus tard. – astroboylrx

2

Voici comment utiliser SFINAE pour obtenir ce que vous voulez:

#include <vector> 
#include <map> 
#include <string> 

using namespace std; 

template<class T> 
    struct Foo { 

    template <class U, typename enable_if<is_pointer<U>::value, int>::type = 0> 
     Foo(U u){} 

    template <class U, typename enable_if<!is_pointer<U>::value, int>::type = 0> 
     Foo(U u){} 

    }; 


int main() 
{ 
    Foo<int> f('a'); // calls second constructor 
    Foo<int> f2("a"); // calls first constructor 
} 

en direct: https://godbolt.org/g/ZPcb5T

+0

Le compilateur détecte float * pour le premier, mais il détecte float pour le second qui prend const flo * et la résolution de surcharge choisit le premier en raison de ce const pas à cause du '**'. – Holt

+0

oups, je voulais supprimer cette partie. Tu as raison. – xaxxon

+0

@xaxxon Merci beaucoup de fournir une solution avec SFIANE et 'std :: enable_if'. C'est lisible et clair. J'apprends toujours le C++, et la raison pour laquelle je réponds si tard est parce que je n'ai pas pu trouver les ressources correctes pour comprendre la partie '= 0' dans les arguments du modèle. Je vois que 'enable_if :: value, int> :: type' est' int' si 'U' est un pointeur dans le premier constructeur. Mais qu'est-ce que '= 0' accomplit dedans? Pourriez-vous s'il vous plaît me donner quelques explications ou quelques conseils/liens? J'apprécie vraiment cela! :-) – astroboylrx

1

Même si les deux constructeurs utilisent le spécificateur explicite et essayer d'éviter les conversions de type, vous devez noter que le premier est tout aussi bon candidat que le second. Si vous U substitut à flotteur * vous obtiendrez:

explicite SmallVec (const float * & scalaire)

qui est tout à fait acceptable et expliquer l'erreur de compilation. Vous pouvez résoudre le problème en changeant le second constructeur à:

template <typename U> 
explicit SmallVec(U* const vec) { // if a vector, copy one by one 
    U* local = vec; 
    for(auto &item : data) { 
     item = static_cast<T>(*local); 
     local++; 
    } 
} 

Cependant, je suggère une façon encore plus explicite:

class ScalarCopy {}; 
class VectorCopy {}; 

... 

template <typename U> 
SmallVec(const U& vec, ScalarCopy); 

template <typename U> 
SmallVec(const U* const vec, VectorCopy); 

et faire des appels explicites:

SmallVec<float> vec1(num, ScalarCopy()); 
SmallVec<float> vec2(arr, VectorCopy()); 
+0

Vous changez la signature avec la première qui n'est pas vraiment bonne puisque vous autorisez le constructeur à modifier la valeur de 'vec'. – Holt

+0

True.C'est une autre raison de préférer la deuxième solution plus explicite. –

+0

Merci pour votre explication et les suggestions. Vous indiquez clairement où est ma faute. :-) – astroboylrx