std :: enable_ifでメンバー関数を条件付きでコンパイル


156

簡単な例を使って、使い方を理解しようとしていますstd::enable_ifこの回答を読んだ後、簡単な例を考え出すのはそれほど難しくないと思いました。std::enable_if2つのメンバー関数から選択して、そのうちの1つだけを使用できるようにしたい。

残念ながら、以下はgcc 4.7でコンパイルできません。何時間も試行した後、私の間違いは何ですかと皆さんに尋ねています。

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gccは次の問題を報告します。

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

g ++が2番目のメンバー関数の間違ったインスタンス化を削除しないのはなぜですか?規格によると、std::enable_if< bool, T = void >::typeブールテンプレートパラメータがtrueの場合にのみ存在します。しかし、なぜg ++はこれをSFINAEと見なさないのですか?オーバーロードのエラーメッセージは、g ++が2番目のメンバー関数を削除しないという問題から発生し、これがオーバーロードであると考えていると思います。


1
わかりませんが、次のように思います。enable_ifはSFINAEに基づいています(置換の失敗はエラーではありません)。ただし、どのオーバーロードを使用するかを決定するためにパラメーターを使用することはできないため、ここでは置換はありません。「true」と「false」をTに依存させる必要があります(簡単な例ではそれを望まなかったことはわかっていますが、おそらく今は簡単すぎます...)
Philipp

3
私もそのことを考えて、使用しようとしたstd::is_same< T, int >::value! std::is_same< T, int >::valueされ、同じ結果が得られます。
evnu 2011

回答:


117

SFINAEが機能するのは、テンプレート引数の引数演繹での置換により、構成の形式が不適切な場合のみです。そのような代用はありません。

私もそのことを考えて、使用しようとしたstd::is_same< T, int >::value! std::is_same< T, int >::valueされ、同じ結果が得られます。

これは、クラステンプレートがインスタンス化されるとき(特に、タイプのオブジェクトを作成するときに発生しますY<int>)、すべてのメンバー宣言(必ずしもその定義/ボディではない!)がインスタンス化されるためです。その中には、そのメンバーテンプレートもあります。Tは既知であり!std::is_same< T, int >::value、falseを返すことに注意してください。だからそれはY<int>含むクラスを作成します

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

std::enable_if<false>::type存在しない型にアクセスするため、宣言の形式が正しくありません。したがって、プログラムは無効です。

メンバーテンプレートenable_ifを、メンバーテンプレート自体のパラメーターに依存させる必要があります。その後、型全体が依然として依存しているため、宣言は有効です。それらの1つを呼び出そうとすると、テンプレート引数の引数の控除が行われ、SFINAEが期待どおりに行われます。これを行う方法については、この質問と対応する回答を参照してください。


14
...わかりやすくするために、役立つ場合に備えて:Yテンプレートクラスのインスタンスがインスタンス化されるとき、コンパイラは実際にはテンプレートメンバー関数をコンパイルしません。ただし、Tこれらのメンバーテンプレートを後でインスタンス化できるように、コンパイラーはメンバーテンプレートDECLARATIONSへの置換を実行します。この障害点はSFINAEではありません。SFINAEは、オーバーロードの解決のために可能な関数のセットを決定するときにのみ適用され、クラスのインスタンス化は、オーバーロードの解決のための関数のセットを決定する場合ではないためです。(そう思うと思います!)
Dan Nissenbaum 2013年

93

私はこの短い例も作成しましたが、うまくいきます。

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

詳しく説明してほしい場合はコメントしてください。私はコードが多かれ少なかれ自明だと思いますが、それからもう一度私はそれを作ったので間違っているかもしれません:)

あなたはここでそれを実際に見ることができます


2
これはVS2012ではコンパイルされません。error C4519: default template arguments are only allowed on a class template
PythonNut 2014年

1
それは残念です。私はgccでのみテストしました。多分これは役立つ:stackoverflow.com/a/17543296/660982
jpihl

1
これは確かにここでの最良の答えであり、まさに私が探していたものです。
Weipeng L 2014

3
Q等しいのに、別のテンプレートクラスを作成する必要があるのはなぜTですか。
ilya1725

1
testメンバー関数をテンプレート化する必要があるからです。両方を同時に存在させることはできません。Qクラステンプレートタイプを転送するだけTです。次のTようにクラステンプレートを削除することもできます:cpp.sh/4nxwしかし、それでは目的が多少損なわれます。
jpihl

13

「うまくいく」ソリューションを探している後発者のために:

#include <utility>
#include <iostream>

template< typename T >
class Y {

    template< bool cond, typename U >
    using resolvedType  = typename std::enable_if< cond, U >::type; 

    public:
        template< typename U = T > 
        resolvedType< true, U > foo() {
            return 11;
        }
        template< typename U = T >
        resolvedType< false, U > foo() {
            return 12;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

コンパイル:

g++ -std=gnu++14 test.cpp 

実行すると:

./a.out 
11

6
あの、なぜあなたは、名前の変更でしょうstd::enable_if_tresolvedType
Qwertie

1
誰もがC ++ 17を使用できるとは限らないからです。
James Yang、

9

この投稿から:

デフォルトのテンプレート引数はテンプレートの署名の一部ではありません

しかし、次のようなことができます:

#include <iostream>

struct Foo {
    template < class T,
               class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
    void f(const T& value)
    {
        std::cout << "Not int" << std::endl;
    }

    template<class T,
             class std::enable_if<std::is_integral<T>::value, int>::type = 0>
    void f(const T& value)
    {
        std::cout << "Int" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.f(1);
    foo.f(1.1);

    // Output:
    // Int
    // Not int
}

動作しますが、これは基本的にはクラス自体ではなくテンプレート関数です...オーバーロードを渡す必要がある場合は、同じプロトタイプの2つの関数のどちらもドロップできません。しかし、アイデアはいいです。OPの例を実際の形に書き直していただけませんか。
user1284631 2014

5

この問題を解決する1つの方法として、メンバー関数の特殊化は、特殊化を別のクラスに入れ、そのクラスから継承することです。他のすべての基礎データにアクセスするには、継承の順序を変更する必要がある場合がありますが、この手法は機能します。

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

この手法の欠点は、メンバー関数ごとにさまざまなことをテストする必要がある場合、それぞれのクラスを作成し、それを継承ツリーにチェーンする必要があることです。これは、一般的なデータメンバーにアクセスする場合にも当てはまります。

例:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};

4

ブール値は、推定されるテンプレートパラメータに依存する必要があります。したがって、修正する簡単な方法は、デフォルトのブールパラメータを使用することです。

template< class T >
class Y {

    public:
        template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
        T foo() {
            return 10;
        }

};

ただし、メンバー関数をオーバーロードする場合は機能しません。代わりに、TickライブラリTICK_MEMBER_REQUIRESから使用するのが最善です。

template< class T >
class Y {

    public:
        TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

        TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

};

次のようなマクロが必要な独自のメンバーを実装することもできます(別のライブラリを使用したくない場合に備えて)。

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all       
    };
};


#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type

それは私にはそのようには機能しませんでした。Maaybe何かが欠けている?OPの例を実際の形に書き直していただけませんか。
user1284631 2014

元の例はオーバーロードでは機能しません。オーバーロードでそれを行う方法を私の答えを更新しました。
Paul Fultz II、2014

0

これが、マクロを使用した私のミニマリストの例です。enable_if((...))より複雑な式を使用する場合は、二重括弧を使用します。

template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;

#define enable_if(value) typename = helper_enable_if<value>

struct Test
{
     template<enable_if(false)>
     void run();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.