19

「 C++クラスにデフォルトのコンストラクター(コンパイラーが提供する型特性以外)があるかどうかをテストする方法はありますか? 」のコードを使用しました。

すべてのテストケースで機能するように少し変更しました。

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;


    // the second version does not work
#if 1
    template<int x, int y> class is_equal {};
    template<int x> class is_equal<x,x> { typedef void type; };

    template< class U >
    static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );
#else
    template<int x> class is_okay { typedef void type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof U() >::type * );
#endif

    template< class U >
    static no sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(yes) };
};

2つのテンプレート引数バージョンでは正しく機能するのに、通常のバージョン(set #if 0)では機能しないのはなぜですか?これはコンパイラのバグですか?VisualStudio2010を使用しています。

次のテストを使用しました。

BOOST_STATIC_ASSERT( is_default_constructible<int>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( !is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );

私はここで本当に途方に暮れています:

  1. 2つのテストが他のバージョンで失敗しています:int[100]NotDefaultConstructible。すべてのテストは、2つのテンプレート引数バージョンで成功します。
  2. VisualStudio2010はをサポートしていませんstd::is_default_constructible。ただし、私の質問は、2つの実装に違いがある理由と、一方が機能し、もう一方が機能しない理由についてです。
4

2 に答える 2

3

g ++の動作(および失敗)が異なるため、これはほぼ確実にコンパイラーのアーティファクト(バグ)のようです。VSの動作が異なる理由を推測することしかできませんが、合理的と思われる推測の1つは、このクラスが次のことです。

template<int x> class is_okay { typedef void type; };

テンプレートパラメータに関係なく同じ定義を持っているので、おそらくコンパイラは分析時にステップをスキップstatic sfinae( typename is_okay< sizeof U() >::type * );し、のパラメータを詳しく調べずに明確に定義されていると見なしますis_okay。したがって、すべてがデフォルトで構築可能であると見なされます。

なぜVSもg++もis_okay::typeプライベートで悩まされないのか、私にはわかりません。どちらもそうあるべきだと思われます。

一方、g ++は、両方のバージョンを同等として扱います。ただし、どちらの場合も、に対して異なるエラーが発生しint[100]ます。それがデフォルトで構築可能であるべきかどうかについては議論の余地があります。あなたはそうすべきではないと思っているようです。g ++ 47std::is_default_constructibleはそうだと思います!その動作(おそらくより標準的です)を取得するには、行内Tでに置き換えることができます。typename boost::remove_all_extents<T>::typeenum

于 2012-09-27T07:40:02.383 に答える
3

(私の答えは、DSの以前の答えから大いに知らされています。)

まず、あなたが持っていることに注意してくださいclass is_okay { typedef void type; }。つまり、typeのプライベートメンバーですis_okay。これは、クラスの外では実際には表示されないため、実際には表示されないことを意味します

template< class U >
static yes sfinae( typename is_equal< sizeof U(), sizeof U() >::type * );

成功することはありません。ただし、SFINAEは元々C++98のこの状況には適用されませんでした。「置換プロセスの一部としてアクセスチェックが[開始]された」のは、DR1170の解決までではありませんでした。[1]

(驚くべきことに、Paolo Carliniはちょうど10日前にそのブログエントリを書いたので、この質問のタイミングは申し分のないものです。このような場合、Carliniによると、4.8より前のGCCはSFINAE中にアクセスチェックをまったく行いませんでした。 GCCがのプライベート性について不満を言っているのを見なかった理由type。正しい動作を確認するには、文字通り2週間以内の最上位のGCCを使用する必要があります。)

Clang(ツリーの最上位)はDRの-std=c++11モードに従いますが、デフォルトのC ++ 03モードでは予期されるエラーが発生します(つまり、ClangはC ++ 03モードのDRに従いません)。これは少し奇妙ですが、下位互換性のためにそうしているのかもしれません。

しかし、とにかくtype、あなたは実際にはそもそもプライベートになりたくありません。あなたが書くつもりだったのはstruct is_equalstruct is_okayです。

この変更により、Clangはすべてのテストケースに合格します。GCC 4.6.1は、を除くすべてのテストケースにも合格していますint[100]。GCCはそれint[100]は大丈夫だと考えていますが、あなたはそれが大丈夫ではないと主張しています。

しかし、コードのもう1つの問題は、テストしていると思うものがテストされないことです。C ++標準の8.5#10節は、非常に明確に述べています。[2]

初期化子が空の括弧のセットであるオブジェクト、つまり、()は値で初期化されます。

したがって、を書くときは、デフォルトで初期化できるsizeof U()かどうかをテストしていません。値を初期化できるかどうかをテストしています!U

...それともあなたですか?少なくとも私のテストケースのいくつかでは、GCCのエラーメッセージはU()、タイプの名前として解釈されていることを示していました—「関数を返すU」—そしてそれint[100]が異なった振る舞いをした理由です。その振る舞いがどのように有効であるかはわかりませんが、ここでの構文の微妙さは本当に理解していません。

本当にデフォルトの初期化をテストするつもりなら、sizeof *new U現在あるところならどこでも同じようなものを使うべきですsizeof U()

ちなみに、int[100] デフォルトで初期化可能な期間です。標準では、配列型をデフォルトで初期化することの意味が明確になっています。

0最後に、コードの奇抜な動作の原因の1つは、装飾されていない(タイプのint)関数を、オーバーロードのセットに1つの関数の取得void *と1つの取得が含まれる関数に渡そうとしていることであるかどうか疑問に思います...。その場合、コンパイラが間違ったものを選んだかどうかは完全に理解できました。0をとる関数に渡してみることをお勧めしintます。

すべてをまとめると、ToTClangとGCC4.6.1の両方で完全に機能する(つまり、アサーションの失敗がない)コードのバージョンがあります。

template< class T >
class is_default_constructible {
    typedef int yes;
    typedef char no;

    template<int x> struct is_okay { typedef int type; };

    template< class U >
    static yes sfinae( typename is_okay< sizeof (*new U) >::type );

    template< class U >
    static no sfinae( ... );

public:
    enum { value = sizeof( sfinae<T>(0) ) == sizeof(yes) };
};

#if __has_feature(cxx_static_assert)
#define BOOST_STATIC_ASSERT(x) static_assert(x, "or fail")
#else
#define dummy2(line) dummy ## line
#define dummy(line) dummy2(line)
#define BOOST_STATIC_ASSERT(x) int dummy(__COUNTER__)[(x) - 1]
#endif

#include <string>

BOOST_STATIC_ASSERT( !is_default_constructible<int()>::value );
BOOST_STATIC_ASSERT( is_default_constructible<bool>::value );
BOOST_STATIC_ASSERT( is_default_constructible<std::string>::value );
BOOST_STATIC_ASSERT( is_default_constructible<int[100]>::value );

BOOST_STATIC_ASSERT( is_default_constructible<const std::string>::value );

struct NotDefaultConstructible {
    const int x;
    NotDefaultConstructible( int a ) : x(a) {}
};

BOOST_STATIC_ASSERT( !is_default_constructible<NotDefaultConstructible>::value );

struct DefaultConstructible {
    const int x;

    DefaultConstructible() : x(0) {}
};

BOOST_STATIC_ASSERT( is_default_constructible<DefaultConstructible>::value );
于 2012-09-27T20:30:35.320 に答える