C ++ SFINAE örnekleri?


122

Daha fazla şablon meta programlamaya girmek istiyorum. SFINAE'nin "ikame başarısızlığı bir hata değildir" anlamına geldiğini biliyorum. Ama biri bana SFINAE için iyi bir kullanım gösterebilir mi?


2
Bu güzel bir soru. SFINAE'yi çok iyi anlıyorum, ancak kullanmak zorunda olduğumu sanmıyorum (kütüphaneler bunu bilmeden yapmadıkça).
Zifre

5
STL , buradaki SSS'lerde biraz farklı bir şekilde ifade ediyor: "İkame Başarısızlığı Bir Fil Değildir"
vulcan kuzgun

Yanıtlar:


72

İşte bir örnek ( buradan ):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

Zaman IsClassT<int>::Yesdeğerlendirilmiştir, 0 dönüştürülemez int int::*int sınıf olmadığı için üye işaretçi bilgisi, böylece. SFINAE yoksa, bir derleyici hatası alırsınız, '0, sınıf dışı int türü için üye işaretçisine dönüştürülemez' gibi bir şey. Bunun yerine, yalnızca ...Two döndüren formu kullanır ve bu nedenle false olarak değerlendirilir, int bir sınıf türü değildir.


8
@rlbond, bu soruya verilen yorumlarda sorunuzu yanıtladım: stackoverflow.com/questions/822059/… . Kısaca: Eğer her iki test işlevi de aday ve uygulanabilirse, "..." en kötü dönüştürme maliyetine sahiptir ve bu nedenle hiçbir zaman diğer işlevin lehine alınmayacaktır. "..." üç nokta, var-arg şeyidir: int printf (char const *, ...);
Johannes Schaub - litb


20
Burada daha tuhaf olan IMO değil, ...daha int C::*çok hiç görmediğim ve yukarı bakmak zorunda kaldığım şey. Bunun ne olduğu ve ne için kullanılabileceğinin
cevabını

1
birisi C :: * 'nin ne olduğunu açıklayabilir mi? Tüm yorumları ve bağlantıları okudum, ancak merak ediyorum, int C :: *, int türünde bir üye işaretçisi olduğu anlamına gelir. ya bir sınıfın int türünde bir üyesi yoksa? Neyi kaçırıyorum? ve test <T> (0) bunda nasıl rol oynuyor? Bir şeyi kaçırıyor olmalıyım
user2584960

92

SFINAEBoole koşullarını kontrol etmek için kullanmayı seviyorum .

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

Oldukça faydalı olabilir. Örneğin, operatör virgülü kullanılarak toplanan bir başlatıcı listesinin sabit bir boyuttan uzun olup olmadığını kontrol etmek için kullandım

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

Liste yalnızca M, N'den küçük olduğunda kabul edilir, bu da başlatıcı listesinin çok fazla öğe içermediği anlamına gelir.

Sözdizimi şu char(*)[C]anlama gelir: öğe türü char ve boyuta sahip bir diziye işaretçi C. Eğer Cyanlışsa (burada 0), o zaman geçersiz bir tip elde ederiz char(*)[0], sıfır boyutlu bir diziye işaretçi: SFINAE, şablonun o zaman yok sayılacağı şekilde bunu yapar.

Şununla ifade edildi boost::enable_ifki, şuna benziyor

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

Pratikte, koşulları kontrol etme yeteneğini çoğu zaman yararlı bir yetenek buluyorum.


1
@Johannes Garip bir şekilde, GCC (4.8) ve Clang (3.2) 0 boyutundaki dizileri bildirmeyi kabul ediyorlar (bu nedenle tür gerçekten "geçersiz" değil), ancak kodunuzda düzgün davranıyor. Tiplerin "düzenli" kullanımına karşı SFINAE durumunda bu durum için muhtemelen özel destek vardır.
akim

@akim: Eğer bu doğruysa (tuhaf mı?! Ne zamandan beri?) o zaman belki M <= N ? 1 : -1işe yarayabilir.
v.oddou

1
@ v.oddou Sadece deneyin int foo[0]. Çok yararlı "0 uzunluklu bir dizi ile biten yapı" hilesine ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html ) izin verdiği için desteklenmesine şaşırmadım .
akim

@akim: evet benim düşündüğüm şey -> C99. Buna C ++ 'da izin verilmez, işte modern bir derleyiciyle elde error C2466: cannot allocate an array of constant size 0
edeceğiniz şey

1
@ v.oddou Hayır, gerçekten C ++ 'yı kastettim ve aslında C ++ 11: hem clang ++ hem de g ++ bunu kabul ediyor ve bunun neden yararlı olduğunu açıklayan bir sayfaya işaret ettim.
akim

16

C ++ 11 SFINAE testlerinde çok daha güzel hale geldi. İşte birkaç yaygın kullanım örneği:

Özelliklere bağlı olarak bir fonksiyon aşırı yüklemesi seçin

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}

Bir tür lavabo deyimi kullanarak, bir üyesi olup olmadığını ve bu üyenin belirli bir türde olup olmadığını kontrol etmek gibi bir tür üzerinde oldukça keyfi testler yapabilirsiniz.

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}

İşte canlı bir örnek: http://ideone.com/dHhyHE Yakın zamanda blogumda SFINAE ve etiket gönderimi hakkında tam bir bölüm yazdım (utanmaz eklenti ama alakalı) http://metaporky.blogspot.de/2014/08/ part-7-statik-sevk-function.html

C ++ 14 itibariyle, esasen benim TypeSink'imle aynı olan bir std :: void_t vardır.


İlk kod bloğunuz aynı şablonu yeniden tanımlar.
TC

İs_integral ve is_floating_point'in her ikisinin de doğru olduğu bir tür olmadığından, bu veya SFINAE en az birini kaldıracağından biri olmalıdır.
odinthenerd

Aynı şablonu farklı varsayılan şablon bağımsız değişkenleriyle yeniden tanımlıyorsunuz. Derlemeyi denediniz mi?
TC

2
Şablon meta programlamada yeniyim, bu nedenle bu örneği anlamak istedim. TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>Bir yerde ve sonra TypeSinkT<decltype(&T::bar)>başka bir yerde kullanmanızın bir nedeni var mı ? Ayrıca &gerekli std::declval<T&>mi?
Kevin Doyon

1
Hakkında TypeSink, C ++ 17 var std::void_t:)
YSC

10

Boost enable_if kütüphane teklifler SFINAE kullanarak için güzel temiz bir arayüz. En sevdiğim kullanım örneklerinden biri Boost.Iterator kitaplığında. SFINAE, yineleyici türü dönüşümleri etkinleştirmek için kullanılır.


4

C ++ 17 muhtemelen özellikleri sorgulamak için genel bir yol sağlayacaktır. Ayrıntılar için N4502'ye bakın, ancak bağımsız bir örnek olarak aşağıdakileri göz önünde bulundurun.

Bu kısım sabit kısımdır, bir başlığa koyun.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

N4502'den alınan aşağıdaki örnek, kullanımı göstermektedir:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

Diğer uygulamalarla karşılaştırıldığında, bu oldukça basittir: azaltılmış bir araç seti ( void_tve detect) yeterlidir. Ayrıca, önceki yaklaşımlardan ölçülebilir derecede daha verimli (derleme zamanı ve derleyici bellek tüketimi) bildirildi (bkz. N4502 ).

İşte GCC öncesi 5.1 için taşınabilirlik ayarlamaları içeren canlı bir örnek .


3

Burada başka bir (geç) var SFINAE örneği dayalı, Greg Rogers bireyin cevabı :

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

Bu şekilde, bir sınıf valueolup olmadığını görmek için 'nin değerini kontrol edebilirsiniz T:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}

int C::*Cevabınızdaki bu sözdizimi ne anlama geliyor? C::*Parametre adı nasıl olabilir ?
Kirill Kobelev

1
Üyeye bir işaretçi. Bazı referanslar: isocpp.org/wiki/faq/pointers-to-members
whoan

@KirillKobelev int C::*, intüye değişkenine işaretçi türüdür C.
YSC

3

İşte SFINAE'nin iyi bir makalesi: C ++ 'ın SFINAE konseptine giriş: bir sınıf üyesinin derleme zamanı iç gözlemi .

Aşağıdaki gibi özetleyin:

/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declvalkolayca oluşturulamayacak türde bir nesneye "sahte referans" veren bir yardımcı programdır. declvalSFINAE yapılarımız için gerçekten kullanışlıdır.

struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}

0

Burada, bir işaretçinin işlev mi yoksa üye sınıfı işaretçisi mi olduğunu belirlemek için şablon işlevi aşırı yüklemesini (doğrudan SFINAE değil) kullanıyorum: ( iostream cout / cerrah üye işlev işaretçilerinin 1 veya true olarak yazdırılmasını düzeltmek mümkün mü? )

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

Baskılar

0. false
1. true
2. true
3. true
4. true

Kod olduğu gibi, (derleyicinin "iyi" olmasına bağlı olarak) true veya false döndüren bir işleve bir çalışma zamanı çağrısı oluşturabilir. is_function_pointer(var)Derleme türünde değerlendirmeye zorlamak istiyorsanız (çalışma zamanında hiçbir işlev çağrısı yapılmaz), constexprdeğişken hünerini kullanabilirsiniz :

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

C ++ standardına göre, tüm constexprdeğişkenlerin derleme zamanında değerlendirilmesi garanti edilir (Derleme zamanında bir C dizesinin hesaplama uzunluğu. Bu gerçekten bir constexpr mi? ).


0

Aşağıdaki kod, derleyicinin bir türün belirli bir yöntemi olup olmadığına bağlı olarak bir aşırı yük seçmesine izin vermek için SFINAE kullanır:

    #include <iostream>
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }
    
    
    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };
    
    struct IntItem {
        int get_int() const {
            return -1;
        }
    };
    
    struct UniversalItem : public IntItem, public FloatItem {};
    
    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

Çıktı:

Şamandıra: 1
Dahili: -1
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.