9

数学的ベクトルに基づいたクラスを作成しようとしています:

template <unsigned N> class Vector{
public:
    Vector() = default;
    Vector(std::initializer_list<double> li) { *this = li;}
    Vector& operator=(std::initializer_list<double>);

private:
    std::array<double, N> x = {}
}

template <unsigned N> inline Vector<N>& Vector<N>::operator=(std::initializer_list<double> li){
     if(N != li.size()) throw std::length_error("Attempt to initialise Vector with an initializer_list of different size.");
     std::copy(li.begin(), li.end(), x.begin());
     return *this;
}

このようなコードを書けるようになりたいです。

Vector<3> a = {1,2,3};
a = {3,5,1};

ユーザーがそのようなコードを書くことを期待するのは自然なことですよね? ただし、間違ったサイズの初期化子リストを使用すると、コンパイル時のエラーが発生するようにしたいと考えstd::arrayています。

 std::array<double, 3> a = {2,4,2,4} //compile time error
 Vector<3> a = {3,5,1,5} //run-time error as of right now

私の最初のアイデアはstd::array、コンストラクター/オペレーターのパラメーターとして使用することでした。これにより、暗黙的な変換が発生し、コンストラクターがstd::arrayコンパイル時エラーからハイジャックします。もちろん、次のようなコードしか書けませんでした。

Vector<3> a({2,3,2}); //fine
Vector<3> b = {2,4,2}; //error, requires two user-defined conversions (list -> array<double,3> -> Vector<3>) 

Variadicメンバーテンプレートを使用することを考えました:

template <typename... Args> Vector(Args... li): x({li...}){
    static_assert(sizeof...(li) == N);
}

非型パラメーターは整数型でなければならないため、そうする必要はありませんtypename...double...しかし、その後、縮小変換エラーに遭遇します

Vector<2> a = {3,2} //error: narrowing conversion of 'li#0' from 'int' to 'double' inside { } [-Wnarrowing]|
 //error: narrowing conversion of 'li#1' from 'int' to 'double' inside { } [-Wnarrowing]|

おそらく [8.5.4]/7 に違反するため

縮小変換は暗黙の変換です

— ソースが定数式であり、変換後の実際の値がターゲットの型に適合し、元の型に変換されたときに元の値が生成される場合を除き、整数型またはスコープのない列挙型から浮動小数点型へ。また

拡張からのパラメーターli...は定数式ではないため、縮小変換エラーが発生します。私が知る限り、関数のパラメータを定数式にすることさえできません (あまり意味がありませんか?)。ですから、そのルートをどのように進めればよいかわかりません。明らかにVector<2> a = {2.,3.}問題なく動作しますが、浮動小数点リテラルを提供することだけを覚えておくという負担がユーザーにかかります。

4

2 に答える 2

5

任意の条件を使用できるように、コンストラクターを可変個引数テンプレートにすることができます。

#include <array>
#include <cstddef>

template<typename T, std::size_t N>
class vector
{
public:
    vector(T&& value)
    : data{static_cast<T>(value)}
    {}
    template<typename U>
    vector(const vector<U,N>& v)
    {
      std::copy(begin(v.data), end(v.data),
                begin(data));
    }
    template<typename U>
    vector(const vector<U,N>& v)
    {
        std::copy(begin(v.data), end(v.data),
                  begin(data));
    }
    template<typename... U,
             typename = typename std::enable_if<sizeof...(U)-1>::type>
    vector(U&&... values)
    : data{static_cast<T>(values)...}
    {
        static_assert(sizeof...(values) == N, "wrong size");
    }
    std::array<T,N> data;
};

int main()
{
    vector<int, 3> v = {1,2,3};
    vector<double, 4> vv = {5,4,3,2};

    vv = {1,2,3,4};

    //vector<float, 3> vf = {1,2,3,4}; // fails to compile
    vector<float,3> vf = v;
}

colir のライブ例

std::arrayカスタムエラーメッセージ、簡単に適応/拡張可能な失敗の条件を取得し、最初にやりたかったように初期化をイニシャライザーに効果的に転送することにより、「変換の絞り込み」の問題を取り除きます。ああ、あなたは無料で割り当てを受けます。

@MMが言及しているように、残念ながら、このソリューションはコピーの構築を台無しにします。enable_if上記のように、可変引数「配列」サイズに を追加することで解決できます。もちろん、割り当て/コピー構築と単一要素のベクトルを台無しにしないように注意する必要があります。これは、これらの特別な場合に 2 つのコンストラクターを追加することで改善されます。

于 2015-11-25T21:24:38.233 に答える