簡體   English   中英

std::enable_if 有條件地編譯成員 function

[英]std::enable_if to conditionally compile a member function

我試圖通過一個簡單的示例來了解如何使用std::enable_if 看完這個答案后,我覺得想出一個簡單的例子應該不會太難。 我想使用std::enable_if在兩個成員函數之間進行選擇,並只允許使用其中一個。

不幸的是,以下內容無法使用 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++不刪除第二個成員function的錯誤實例化? 根據標准, std::enable_if< bool, T = void >::type僅在 boolean 模板參數為真時存在。 但為什么 g++ 不認為這是 SFINAE? 我認為重載錯誤消息來自g++沒有刪除第二個成員function的問題,並認為這應該是一個過載。

SFINAE 僅在模板參數的參數推導中的替換使構造格式錯誤時才有效。 沒有這樣的替代。

我也想到了這一點,並嘗試使用std::is_same< T, int >::value! 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訪問不存在的類型,因此該聲明std::enable_if<false>::type 因此您的程序無效。

您需要使成員模板的enable_if依賴於成員模板本身的參數。 那么聲明是有效的,因為整個類型仍然是依賴的。 當您嘗試調用其中之一時,會對其模板參數進行參數推導,並且 SFINAE 會按預期發生。 請參閱此問題以及有關如何執行此操作的相應答案。

我做了這個簡短的例子,它也有效。

#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;
}

如果你想讓我詳細說明,請評論。 我認為代碼或多或少是不言自明的,但是我又做了一遍,所以我可能是錯的:)

你可以在這里看到它的實際效果。

對於那些正在尋找“有效”解決方案的后來者:

#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

這篇文章:

默認模板參數不是模板簽名的一部分

但是可以做這樣的事情:

#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
}

解決這個問題的一種方法,成員函數的特化是把特化放到另一個類中,然后從那個類繼承。 您可能必須更改繼承順序才能訪問所有其他底層數據,但這種技術確實有效。

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.
};

布爾值需要依賴於推導的模板參數。 所以一個簡單的修復方法是使用默認的布爾參數:

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;
        }

};

您也可以像這樣實現自己的成員 requires 宏(以防萬一您不想使用其他庫):

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

這是我使用宏的極簡示例。 使用更復雜的表達式時,請使用雙括號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();
}
// Try this one:

#include <iostream>
#include <type_traits>

// suppose you want to disable certain member functions based on the tag
struct FooTag;
struct BarTag;

// macro to save some typings in the following
// note that a dummy typename is involved in both the 
// first and second parameters. 
// this should be different than the template parameter of the class (typename T for Widget below)

#define EnableIfFoo(T) \
template <typename Dummy = void, typename = \
          typename std::enable_if<std::is_same<FooTag, T>::value, Dummy>::type>

#define EnableIfBar(T) \
template <typename Dummy = void, typename = \
          typename std::enable_if<std::is_same<BarTag, T>::value, Dummy>::type>

template <typename T>
class Widget {
public:
    // enable this function only if the tag is Bar
    EnableIfFoo(T)
    void print() const { std::cout << "I am a Foo!" << std::endl; }
    
    // enable this function only if the tag is Foo
    EnableIfBar(T)
    void display() const { std::cout << "I am a Bar!" << std::endl; }
};


int main() {
    
    // instantiate a widget with tag Foo
    // only print is enabled; display is not
    Widget<FooTag> fw;
    fw.print();
    //fw.display(); // compile error !!
    
    // instantiate a Widget using tag Bar
    // only display is enabled; print is not
    Widget<BarTag> bw;
    bw.display();
    //bw.print(); // compile error !!
    
    return 0;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM