7

C++ (具体的には、MinGW の g++ の実装) が混乱しています。任意の数の任意のタイプの要素を含む数学的 Vector クラスがあります。要素のタイプと要素の数は、コンパイル時に指定されます。

Vector クラスは、そのコンストラクターの 1 つと、私が「サイズ変更」オペレーターと呼んでいるものとの間で混乱しています。サイズ変更演算子を使用すると、プログラマはあるサイズのベクトルを別の任意のサイズのベクトルにキャストできます。キャスト ベクトルに基本ベクトルよりも多くの要素がある場合、1 でパディングされます。実装は次のとおりです。

/*
 * resize operator:
 * T is the type of element the base vector holds
 * N is the number of elements the base vector holds
 * rN is the size of the new vector
 */
template<typename T, unsigned int N, unsigned int rN>
operator Vector<T, rN>() const 
{
    Vector<T, rN> resize;

    for (unsigned int i = 0; i < rN; i++)
    {
        resize[i] = i < N ? this->elements[i] : 1;
    }

    return resize;
}

vector クラスには、要素 (T 型でなければならない) の任意の組み合わせと任意の数の Vector (任意の数の要素を含むことができ、T 型でなければならない) をいくつでも取ることができるタイプセーフな可変引数コンストラクタもあります。提供されたベクトルの要素の数に追加された裸の要素の数が、構築するベクトルに含まれる要素の数と等しい限り。

したがって、これは有効です。

vec3 foo(vec2(1, 2), 3);

しかし、これではありません。

vec3 bar(vec4(1, 2, 3, 4), 5);

カウンターを使用してすべての要素を再帰することにより、コンパイル時に適切な数の要素が提供されていることを確認します。次に、静的アサーションを使用して、カウンターがベクトルに含まれる要素の数になるようにします。次のコードを除いて、これは通常うまくいきます。

vec4 bar(1, 2, 3, 4);
(vec3) bar; //PROBLEM HERE

何が起こっているかというと、実際にはサイズ変更演算子を呼び出す必要があるときに、(vec3) bar が可変長コンストラクターを要求していると C++ が考えていることです。それらを明示的にしようとしましたが、うまくいきませんでした。可変引数コンストラクターではなく上記のコードがある場合、C++ がサイズ変更演算子を使用するようにするにはどうすればよいですか?

要するに、これを使用するように C++ に指示する方法は次のとおりです。

//resize operator
template<typename T, unsigned int N, unsigned int rN>
Vector<T, N>::operator Vector<T, rN>();

これの代わりに:

//constructor
template<typename T, unsigned int N, typename ... Args>
Vector<T, N>::Vector(Args ... arguments);

このコードがある場合:

(vec3) someVec4;

明確でない場合のために、vec3 と vec4 は次のように定義されています。

typedef Vector<float, 3> vec3;
typedef Vector<float, 4> vec4;

編集

ニュース、皆さん!static_cast(someVec4) を使用しても、vec4 引数を使用して vec3 コンストラクターを呼び出します。どうしてか分かりません。

別の編集

コンストラクターを明示的にすると、暗黙的なキャストは機能しますが、明示的なキャストは機能しません。つまり、このコードは機能します。

vec3 foo = someVec4;

しかし、このコードでも静的アサーション エラーが発生します。

vec3 foo = static_cast<vec3>(someVec4);

可変引数コンストラクターを明示的に宣言したため、基本的に意味がありません。したがって、そこで呼び出すべきではありません。

また、リクエストに応じて、ここにSSCCEがあります

これのTL;DRバージョンは、型キャスト演算子を明示的に呼び出そうとするとコードが明示的なコンストラクターを呼び出しますが、暗黙的に呼び出そうとすると呼び出しません。

4

4 に答える 4

4

混乱はありません。コンストラクターは常に変換関数よりも優先され、あなたの場合、型は常にあらゆる種類の引数から構築可能です。以下は、大幅に削減された例です。

struct foo {
    template<typename T>
    foo(T t);
}

template<typename T>
foo::foo(T)
{ static_assert( std::is_same<T, int>::value, "" ); }

Tテンプレート コンストラクター宣言に注意してください (わざと定義から宣言を分離しました) 。のみが正しいプログラムを生成しますが、std::is_constructible<foo, T>::valueはすべてのに適用されます。他のタイプは、コンストラクターがインスタンス化されたときにトリガーされます。Tintstatic_assert

あなたが望むものを達成するための秘密のソースがあり、その名前は SFINAE です。大まかに説明すると (そうでない場合に備えて)、潜在的なエラーをテンプレートの本体から宣言のどこかに移動すると、そのようなエラーを生成する特殊化はオーバーロード解決のプロセスで破棄されます。コードに入れるには:

struct foo {
    template<
        typename T
        , typename std::enable_if<
            std::is_same<T, int>::value
            , int
        >::type...
     >
     foo(T t);
};

これは、前の不自然な例の SFINAE バージョンになります。このような宣言ではfoo f = 42.;、前と同じ種類のエラーは発生しません。コンパイラーは、コンストラクターがまったく存在しないかのように、からdoubleへの適切な変換がないなどと文句を言います。fooそのようなコンストラクターが存在しない場合、適切な変換演算子が検索されるようにルールが指示されているため、これが必要です。(まあ、それが大きな助けになるわけではありませんがdouble、気にしないでください。)

SFINAE を利用するにはいくつかの方法があることに注意してください。これはたまたま私のお気に入りの形式です。SFINAE について学ぶことで、他の人を見つけることができます。(記録としては、テンプレート エイリアスを適切に使用すれば、最終的にEnableIf<std::is_same<T, int>>....

于 2012-08-11T13:01:41.387 に答える
3

コンストラクターを明示的にして使用します。

vec4 someVec4; 
// ....
vec3 someVec3 = someVec4;
于 2012-08-11T11:52:20.557 に答える
2

SSCCEを見ると、適用できるクリーンアップ手順がいくつかあります。

ユニバーサル コンストラクター テンプレートの大きな問題は、非テンプレート コンストラクターが完全に一致しない限り、すべてに一致することです。cv-qualification さえ間違っていれば、ユニバーサル コンストラクター テンプレートが選択されます。同様の問題が発生したとき、最初のパラメーターとしてマーキング値を追加するように提案されました。

enum my_marker { mark };
//...
template<typename T, unsigned int N>
class Vector
{
    //...
    template<typename ... Args>
    explicit Vector(my_marker, Args ... args);
};
//...
Vector<int, 4>  va( mark, a1, a2 );

他のコンストラクターはこのマーカーを使用しないため、それらを区別できるようになりました。ところで、T値を取ることができるコンストラクターと別のオーバーラップがあります。

template<typename T, unsigned int N>
class Vector
{
    //...
    Vector( T empty );
    Vector( std::initializer_list<T> set );
    //...
};
//...
Vector<int, 4>  vb{ 5 };  // always chooses the list ctr
Vector<int, 4>  vc( 6 );  // I think this uses the single-entry ctr.

関数の引数として配列がある場合、デフォルトではポインタとして扱われ、サイズ情報は無視されます。サイズを維持する必要がある場合は、参照渡しする必要があります。

template<typename T, unsigned int N>
class Vector
{
    //...
    Vector( T const (&set)[N] );  // "T set[N]" -> "T *set"
    //...
};
//...
int             aa[ 4 ] = { 1, 2, 3, 4 }, bb[ 3 ] = { 5, 6, 7 };
Vector<int, 4>  vd( aa );  // The new signature won't accept bb.

この配列からポインタへの変換により、配列を直接代入できなくなりますが、特殊関数を計算するときに暗黙的に代入できます。これは、代入演算子が不要であることを意味します。デフォルトのコードは正しいことを行います。

イテレータについて聞いたことがありますか?もしそうなら、それらに加えて委任コンストラクター、標準アルゴリズム、および初期化子を使用すると、コードを削減できます。

#include <algorithm>
#include <cassert>
#include <initializer_list>

enum mark_t  { mark };

template< typename T, unsigned N >
class Vector
{
    // The "set" functions are unnecessary, see below.
public:
    // The automatically defined copy-ctr, move-ctr, copy-assign, and
    // move-assign are OK.

    T elements[N];

    Vector()  : elements{}  {}
    // Vector()  : Vector( T{} )  {}  // ALTERNATE
    // Can be removed if following constructor uses a default argument.

    Vector(T empty)
    // Vector(T empty = T{})  // ALTERNATE
    { std::fill( elements, elements + N, empty ); }

    Vector(T const (&set)[N])
    { std::copy( set, set + N, elements ); }

    Vector(std::initializer_list<T> set)
        : elements{}
    {
        assert( set.size() <= N );
        std::copy( set.begin(), set.end(), elements );
        // If you were willing to use std::for_each, why not use a more
        // appropriate algorithm directly?  The lambda was overkill.
        // WARNING: there's an inconsistency here compared to the cross-
        // version constructor.  That one fills unused spots with ones,
        // while this one does it with zeros.
        // WARNING: there's an inconsistency here compared to the single-
        // value constructor.  That one fills all elements with the same
        // value, while this one uses that value for the first element but
        // fills the remaining elements with zeros.
    }

    template<typename ... Args>
    explicit Vector( mark_t, Args ... args)
        : elements{ args... }
        //: elements{ static_cast<T>(args)... }  // ALTERNATE
    {}
    // Array members can now be directly initialized in the member part
    // of a constructor.  They can be defaulted or have each element
    // specified.  The latter makes the private "set" methods unnecessary.
    // The compiler will automatically issue errors if there are too
    // many elements for the array, or if at least one "Args" can't be
    // implicitly converted to "T", or if you have less than "N" elements
    // but "T" doesn't support default-initialization.  On my system, the
    // example "main" flags int-to-float conversions as narrowing and post
    // warnings; the alternate code using "static_cast" avoids this.

    template < unsigned R >
    explicit Vector( Vector<T, R> const &v )
        : Vector( static_cast<T>(1) )
    { std::copy( v.elements, v.elements + std::min(R, N), elements ); }

    T &operator [](unsigned int param)
    { return this->elements[param]; }
    const T &operator [](unsigned int param) const
    { return this->element[param]; }
};

typedef Vector<float, 2> vec2;
typedef Vector<float, 3> vec3;
typedef Vector<float, 4> vec4;

int main()
{
    vec4 someVec4(mark, 1, 2, 3, 4);
    vec3 foo = static_cast<vec3>(someVec4);

    return 0;
}
于 2012-08-14T15:42:37.957 に答える
1

コードを機能させる最も簡単な方法は、変換演算子を変換コンストラクターに置き換えることだと思います。そのコンストラクターは可変引数コンストラクターよりも特殊化されているため、常に優先する必要があります。

于 2012-08-11T13:53:35.160 に答える