13

次のコードを抜粋しましたが、コンパイルされません。

#include <iostream>

struct A {
    void foo() {}
};

struct B : public A {
    using A::foo;
};

template<typename U, U> struct helper{};

int main() {
    helper<void (A::*)(), &A::foo> compiles;
    helper<void (B::*)(), &B::foo> does_not_compile;

    return 0;
}

&B::fooに解決されるためコンパイルされないため&A::foo、提案された型と一致しませんvoid (B::*)()。これは、非常に特定のインターフェイス (特定の引数の型と出力の型を強制しています) をチェックするために使用している SFINAE テンプレートの一部であるため、チェックを読みやすく保ちながら、継承とは無関係に機能するようにしたいと考えています。

私が試したことは次のとおりです。

  • 引数の 2 番目の部分のキャスト:

    helper<void (B::*)(), (void (B::*)())&B::foo> does_not_compile;

    残念ながら、2 番目の部分が定数式として認識されず、失敗するため、これは役に立ちません。

  • それを確認するために、参照を変数に割り当ててみました。

    constexpr void (B::* p)() = &B::foo; helper<void (B::* const)(), p> half_compiles;

    このコードは clang 3.4 で受け入れられますが、g++ 4.8.1 では拒否され、誰が正しいのかわかりません。

何か案は?

編集:多くのコメントが問題のより具体的なバージョンを求めているため、ここに書きます:

私が探しているのは、クラスが特定のインターフェースを尊重していることを明示的に確認する方法です。このチェックは、テンプレート化された関数の入力引数を検証するために使用され、それらの関数が必要とするコントラクトを尊重し、クラスと関数に互換性がない場合にコンパイルが事前に停止するようにします (つまり、型特性の種類のチェック)。

したがって、要求する各メンバー関数の戻り値の型、引数の型と数、constness などを確認できる必要があります。最初の質問は、一致を検証するために使用しているより大きなテンプレートのチェック部分でした。

4

4 に答える 4

4

特定の型 T のインターフェイスの存在を単に確認したい場合は、それを行うためのより良い方法があります。以下に一例を示します。

template<typename T>
struct has_foo
{
    template<typename U>
    constexpr static auto sfinae(U *obj) -> decltype(obj->foo(), bool()) { return true; }

    constexpr static auto sfinae(...) -> bool { return false; }

    constexpr static bool value = sfinae(static_cast<T*>(0));
};

テストコード:

struct A {
    void foo() {}
};

struct B : public A {
    using A::foo;
};

struct C{};

int main() 
{
    std::cout << has_foo<A>::value << std::endl;
    std::cout << has_foo<B>::value << std::endl;
    std::cout << has_foo<C>::value << std::endl;
    std::cout << has_foo<int>::value << std::endl;
    return 0;
}

出力 (デモ):

1
1
0
0

それが役立つことを願っています。

于 2014-05-06T17:25:17.107 に答える
3

これは、テストに合格する単純なクラスです(そして、多くの特殊化は必要ありません:))。過負荷時にも機能しfooます。チェックしたい署名は、テンプレート パラメーターにすることもできます (それは良いことですよね?)。

#include <type_traits>

template <typename T>
struct is_foo {
    template<typename U>
    static auto check(int) ->
    decltype( static_cast< void (U::*)() const >(&U::foo), std::true_type() );
    //                     ^^^^^^^^^^^^^^^^^^^
    //                     the desired signature goes here

    template<typename>
    static std::false_type check(...);

    static constexpr bool value = decltype(check<T>(0))::value;
};

ライブの例はこちら.

編集 :

の 2 つのオーバーロードがありcheckます。どちらも整数リテラルをパラメーターとして受け取ることができ、2 番目のパラメーター リストには省略記号があるため、両方のオーバーロードが実行可能な場合、オーバーロードの解決において最適とは言えません ( elipsis-conversion-sequenceは他の変換シーケンスよりも悪いです)。 . valueこれにより、後で特性クラスのメンバーを明確に初期化できます。

2 番目のオーバーロードは、最初のオーバーロードがオーバーロード セットから破棄されたときにのみ選択されます。これは、テンプレート引数の置換が失敗し、エラーではない場合に発生します (SFINAE)。

それを実現するのは、内部のコンマ演算子の左側にあるファンキーな式decltypeです。次の場合、形式が崩れる可能性があります。

  1. サブ式の形式&U::fooが正しくありません。これは、次の場合に発生する可能性があります。

    • Uクラス型ではない、または
    • U::fooアクセスできない、または
    • ありませんU::foo
  2. static_cast結果のメンバー ポインターは、ターゲットの型にすることはできません

それ自体があいまいである場合、ルックアップ&U::fooは失敗しないことに注意してください。U::fooこれは、C++ 標準の13.4(オーバーロードされた関数のアドレス [over.over] ) にリストされている特定のコンテキストで保証されています。そのようなコンテキストの 1 つは、明示的な型変換です (static_castこの場合)。

この式は、クラスがどこから派生したかT B::*に変換可能であるという事実も利用します(ただし、その逆はできません)。この方法では、iavr の answer のようにクラス タイプを推測する必要はありません。T D::*DB

valuemember は、またはvalueのいずれかでtrue_type初期化されますfalse_type


ただし、このソリューションには潜在的な問題があります。検討:

struct X {
    void foo() const;
};

struct Y : X {
    int foo();   // hides X::foo
};

に遭遇すると、 の名前検索が停止するため、is_foo<Y>::valueが与えられます。それが望ましい動作でない場合は、ルックアップを実行するクラスを のテンプレート パラメータとして渡し、それを の代わりに使用することを検討してください。falsefooY::foois_foo&U::foo

それが役立つことを願っています。

于 2014-05-07T16:23:03.383 に答える