üye işlevini koşullu olarak derlemek için std :: enable_if


156

Nasıl kullanılacağını anlamak için çalışmak için basit bir örnek almaya çalışıyorum std::enable_if. Bu cevabı okuduktan sonra basit bir örnek bulmanın çok zor olmaması gerektiğini düşündüm. Kullanmak istediğim std::enable_ifiki üye fonksiyonları arasında seçim ve bunlardan yalnızca birini kullanılacak izin vermek.

Ne yazık ki, aşağıdaki gcc 4.7 ile derlenmez ve saatlerce ve denedikten sonra size hatamın ne olduğunu soruyorum.

#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 aşağıdaki sorunları bildirir:

% 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()'

Neden g ++ ikinci üye işlevi için yanlış örneği silmiyor? Standarda göre, std::enable_if< bool, T = void >::typeyalnızca boolean şablon parametresi doğru olduğunda bulunur. Ama neden g ++ bunu SFINAE olarak görmüyor? Aşırı yükleme hata iletisi, g ++ ikinci üye işlevini silmez ve bunun bir aşırı yük olması gerektiğine inanıyor sorundan geldiğini düşünüyorum.


1
Emin değilim, ama aşağıdaki olduğunu düşünüyorum: enable_if SFINAE dayanmaktadır (ikame hatası bir hata değildir). Bununla birlikte, burada herhangi bir ikame işleminiz yoktur, çünkü hangi aşırı yüklenmenin kullanılacağını belirlemek için hiçbir parametre kullanılamaz. (... Ben basit örnekte bunu yapmak istemediğini biliyorum, ama şimdi muhtemelen çok basit) Sen "gerçek" T. bağlıdır "false" und yapmalıdır
Philipp

3
Bunu da düşündüm ve kullanmaya çalıştım std::is_same< T, int >::valueve ! std::is_same< T, int >::valuebu da aynı sonucu veriyor.
evnu

Yanıtlar:


117

SFINAE yalnızca bir şablon argümanının bağımsız değişkeninde yer değiştirme yerine geçmesi yapıyı kötü biçimlendirirse çalışır. Böyle bir ikame yoktur.

Bunu da düşündüm ve kullanmaya çalıştım std::is_same< T, int >::valueve ! std::is_same< T, int >::valuebu da aynı sonucu veriyor.

Çünkü sınıf şablonu başlatıldığında ( Y<int>diğer durumlar arasında bir tür nesnesi oluşturduğunuzda olur ), tüm üye bildirimlerini (mutlaka tanımlarını / gövdelerini değil!) Başlatır. Bunların arasında üye şablonları da vardır. O Tzaman bilinir ve !std::is_same< T, int >::valueyanlış verir. Böylece 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>::typeBu deklarasyon kötü oluşturulur böylece, var olmayan tip erişir. Bu nedenle programınız geçersiz.

Üye şablonlarının enable_ifüye şablonun kendisinin bir parametresine bağlı olmasını sağlamanız gerekir . O zaman tüm tipler hala bağımlı olduğu için bildirimler geçerlidir. Bunlardan birini aramaya çalıştığınızda, şablon bağımsız değişkenleri için bağımsız değişken kesinti gerçekleşir ve SFINAE beklendiği gibi gerçekleşir. Bu soruya ve bunun nasıl yapılacağına ilişkin ilgili cevaba bakınız .


14
... Açıklığa kavuşturmak için, yararlı olması durumunda: YTemplate sınıfının bir örneği başlatıldığında, derleyici aslında şablon üyesi işlevlerini derlemez; bununla birlikte, derleyici T, bu şablonların daha sonra başlatılabilmesi için üye şablonunun bildirimlerini yerine getirir. Bu hata noktası SFINAE değildir, çünkü SFINAE yalnızca aşırı yük çözünürlüğü için olası işlevler kümesi belirlenirken geçerlidir ve bir sınıfı başlatmak aşırı yük çözünürlüğü için bir işlevler dizisi belirlemek durumunda değildir. (Ya da sanırım!)
Dan Nissenbaum

93

Ben de işe yarayan bu kısa örneği yaptım.

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

Eğer detaylandırmamı istiyorsan yorum yap. Kod az ya da çok kendi kendini açıklayıcı olduğunu düşünüyorum, ama sonra tekrar ben yanlış olabilir bu yüzden yaptım :)

Burada çalışırken görebilirsiniz .


2
Bu VS2012'de derlenmez. error C4519: default template arguments are only allowed on a class template.
PythonNut

1
Bu talihsizlik. Sadece gcc ile test ettim. Belki bu yardımcı olur: stackoverflow.com/a/17543296/660982
jpihl

1
bu kesinlikle en iyi cevap ve tam olarak aradığım şey.
Weipeng L

3
QEşit olmasına rağmen neden başka bir şablon sınıfı oluşturmaya ihtiyaç var T?
ilya1725

1
Çünkü testüye işlevini şablonlamanız gerekir . Her ikisi de aynı anda var olamaz. Qsınıf şablonu türünü iletir T. Sınıf şablonunu Tşu şekilde kaldırabilirsiniz : cpp.sh/4nxw, ancak bu da amacı yendi.
jpihl

13

"Sadece işe yarar" bir çözüm arayan geç gelenler için:

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

Şununla derleyin:

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

Koşu:

./a.out 
11

6
Um, neden adlandırmak istiyorum std::enable_if_tiçin resolvedType.
Qwertie

1
Çünkü herkes C ++ 17'yi çok çeşitli nedenlerden dolayı kullanamaz.
James Yang

9

Gönderen bu mesaja:

Varsayılan şablon bağımsız değişkenleri, şablonun imzasının bir parçası değildir

Ama böyle bir şey yapılabilir:

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

İşe yarıyor, ama bu sınıfın kendisi değil, temelde cazip işlevler ... İkisi de aynı şekilde prototiplenmiş iki işlevden birinin düşmesine izin vermez (aşırı yüklemeyi geçmeniz gerektiğinde). Ancak, fikir güzel. OP örneğini çalışan bir formda yeniden yazabilir misiniz lütfen?
user1284631

5

Bu sorunu çözmenin bir yolu, üye işlevlerinin uzmanlaşması, uzmanlığı başka bir sınıfa yerleştirip o sınıftan miras almaktır. Diğer tüm temel verilere erişmek için kalıtım sırasını değiştirmeniz gerekebilir, ancak bu teknik işe yarar.

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

Bu tekniğin dezavantajı, farklı üye işlevleri için birçok farklı şeyi test etmeniz gerektiğinde, her biri için bir sınıf oluşturmanız ve miras ağacında zincirlemeniz gerektiğidir. Bu, yaygın veri üyelerine erişmek için geçerlidir.

Ör:

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

Boole, çıkartılan şablon parametresine bağlı olmalıdır. Bu yüzden düzeltmenin kolay bir yolu, varsayılan bir boole parametresi kullanmaktır:

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

};

Ancak, üye işlevini aşırı yüklemek istiyorsanız bu çalışmaz. Bunun yerine, kullanım için elinden geleni TICK_MEMBER_REQUIRESgelen Kene kütüphanesinde:

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

};

Kendi üyenizi aşağıdaki gibi makrolar da uygulayabilirsiniz (başka bir kütüphane kullanmak istemiyorsanız):

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

Benim için böyle olmadı. Maaybe bir şey mi eksik? OP örneğini çalışan bir formda yeniden yazabilir misiniz lütfen?
user1284631

Orijinal örnek aşırı yükleme ile çalışmaz. Cevabımı aşırı yükleme ile nasıl yapabileceğinizi güncelledim.
Paul Fultz II

0

İşte bir makro kullanarak minimalist örneğim. enable_if((...))Daha karmaşık ifadeler kullanırken çift ​​parantez kullanın .

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();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.