2016-04-24 9 views
5

代わりにスカラーを取る別の人をオーバーロードする引数として配列をとるコンストラクタを構築しようとしています。コードは以下のとおりです。C++クラスのテンプレートコンストラクタ - 配列(U *)のオーバーロード参照(U&)が失敗しました

#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; 
} 

コンパイラは

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

はもちろん、vec2(arr)はまだ最初のコンストラクタを呼び出すと文句を言い。ただし、template <typename U>を削除し、UTに置き換えてください。プログラムは正常に動作します。これを修正するにはどうしたらよいですか?

何か提案がありがとうございます!

+0

ヒント:最初のコンストラクタは両方とも呼び出されます。 – LogicStuff

+0

@LogicStuff迅速なコメントをいただきありがとうございます。はい、最初のコンストラクタが再び呼び出されます。これはおそらく配列(または配列へのポインタ)が参照渡しされることを意味しますか?しかし、どのようにプログラムに2番目のコンストラクタを見つけるか、あるいは最初のメソッドで引数がスカラーか配列かどうかを区別する方法を教えてください。もう少しヒントをお願いしますか? – astroboylrx

+0

あなたは[SFINAE](http://en.cppreference.com/w/cpp/language/sfinae)またはタグディスパッチを使用する必要があります。 – LogicStuff

答えて

2
をあなたはに2つ目のコンストラクタを変更することで問題を解決することができ

配列を引数として取るコンストラクタを構築しようとしています

(...)

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

あなたは、配列を取ることはありません。 をポインタを指しても指していなくてもよく、配列を指していても、その配列に少なくとも3つの要素があると言う人はいますか?それは深刻な設計上の欠陥です。構文は恐ろしいであっても、あなたが参照またはconst参照によって生の配列を取ることができない++

C:

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

コンストラクタの実装も、その後異なります

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

を見ますしかし、mainに、問題は深刻になります。配列を動的に割り当てるには、new[]を使用します。それはすでに非常に悪い考えです。偶然にも、あなたの例にはdelete[]がありません。代わりにローカル配列を使用してみませんか?

float arr[3]; 

これは、プログラムが正しく実行され、おそらくコンパイルとなりますが、あなたが唯一の有効な値に、配列の3番目の要素を設定するので、あなたのコード内の未定義の動作は、まだあります。他の2つの要素は初期化されずに残っており、初期化されていないfloatからの読み込みは、正式にコピーしても未定義の動作になります。

だから、より良いそれを作る:それに加えて

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

を、C++ 11は、一般的に物事が少しより安全になり、構文が向上し、std::arrayを使用していただけます。ここに完全な例があります:

#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

この詳細な説明ありがとうございます!それは役に立ちます。はい、それは私のせいでした。私はもともと 'arr [3]'を使っていましたが、コンストラクタへのポインタを与えるべきだと思ったので、 'new []'に変更しましたが 'delete []'を忘れました。今、私は配列にポインタを渡すことについて間違っていたことを理解しています。はい、関数内で定義された変数は初期化されず、これらの未定義の振る舞いを避ける必要があります。私はあなたがそれを指摘していただきありがとうございます。 :-)私は後で質問するときには注意が必要です。 – astroboylrx

2

は、ここであなたが望む結果を得るためにSFINAEを使用する方法は次のとおりです。

ライブ
#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 
} 

https://godbolt.org/g/ZPcb5T

+0

コンパイラは、最初のフロートでは 'float *'を検出しますが、 'float'を検出して' const float * 'をとり、' const'のためにオーバーロード分解能が最初のものを選択します。 '**'。 – Holt

+0

おっと、私はその部分を削除するつもりでした。あなたが正しい。 – xaxxon

+0

@xaxxon SFIANEと 'std :: enable_if'を使って解決策を提供してくれてありがとうございます。それは読みやすく、明確です。私はまだC++を勉強しています。私がテンプレート引数の '= 0'部分を理解するのに正しいリソースを見つけることができなかったので、私が遅く答える理由はあります。 'enable_if :: value、int> :: type'は' U'が最初のコンストラクタのポインタであれば 'int'です。しかし、 '= 0'はそれで何が達成されますか?いくつかの説明やヒント/リンクを教えてください。ほんとうにありがとう! :-) – astroboylrx

1

両方のコンストラクタを明示的に指定子を使用し、型変換を回避しようとしてもかかわらず、あなたが最初であることに注意すべきです第二のものと同じくらい良い候補者です。

明示SmallVec(のconst float型* &スカラー)

完全に許容可能であるとコンパイルエラーを説明します:あなたはあなたが得る*フロートのためのUに置き換えた場合。しかし、私はもっと明示的な方法を提案

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++; 
    } 
} 

class ScalarCopy {}; 
class VectorCopy {}; 

... 

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

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

をして明示的に呼び出します

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

コンストラクターに 'vec'の値を変更させることができるので、最初のシグニチャーでシグネチャーを変更しています。 – Holt

+0

真。これが第2のより明白な解決策を好むもう一つの理由です。 –

+0

あなたの説明と提案をありがとう。あなたは私の欠陥がどこにあるのかを明確にします。 :-) – astroboylrx

関連する問題