SFINAE und enable_if



  • Hi,

    ich versuche grade per Metaprogrammierung bestimmte Methoden einer Klasse in Abhängigkeit ihrer Template-Typen umzusetzen.

    #include <type_traits>
    
    template<std::size_t R, std::size_t N>
    struct IsRankN
    {
       static const bool value = (R == N);
    };
    
    template<typename T,std::size_t R>
    class Test
    {
       using IsRank1 = std::enable_if<IsRankN>R,1>>; // Test auf R = 1
       using IsRank2 = std::enable_if<IsRankN>R,2>>; // Test auf R = 2
    
       // soll nur existieren für R = 1
       typename IsRank1::type f( std::size_t d0 );
    
       // soll nur existieren für R = 2
       typename IsRank2::type f( std::size_t d0, std::size_t d1 );
    };
    
    int main()
    {
       Test<int,1> t1;
       t1.f( 1 );
    
       Test<int,2> t2;
       t2.f( 1,2 );
    }
    

    Ich probiere da schon eine Weile rum, krieg´s aber iwie nicht hin. Kann mir da jemand vielleicht ein funktionierendes Beispiel geben?



  • Das ist eigentlich nicht so der ganz typische Ansatz für SFINAE weil es bei dir vom Template Parameter der Klasse und nicht der Funktion abhängen soll. Dazu muss man eine zusätzliche Indirektion über Default Template Parameter einbauen:

    template<typename T,std::size_t R>
    class Test
    {
    public:
       // soll nur existieren für R = 1
       template<std::size_t X = R>
       typename std::enable_if<X == 1>::type f( std::size_t d0 )
       {
       }
    
       // soll nur existieren für R = 2
       template<std::size_t X = R>
       typename std::enable_if<X == 2>::type f( std::size_t d0, std::size_t d1 )
       {
       }
    };
    

    Abkürzen der Typen über using funktioniert ziemlich sicher nicht. Und dein IsRankN Struct ist eigentlich unnötig. So eine Hilfsklasse braucht man nur wenn man Typen vergleichen will.

    Man könnte etwas ähnliches übrigens auch über Spezialisierung der Klasse erreichen.


  • Mod

    Ich probiere da schon eine Weile rum, krieg´s aber iwie nicht hin.

    Natürlich nicht, die Instantiierung der Klasse impliziert die Instantiierung aller enthaltenen Deklarationen.

    Du möchtest evt. partiell Spezialisieren.

    template <typename, std::size_t> 
    class Test; 
    template <typename T>
    class Test<T, 1> {
       void f( std::size_t d0 ); 
    };
    template <typename T>
    class Test<T, 2> {
       void f( std::size_t d0, std::size_t d1 ); 
    };
    
    int main() 
    { 
       Test<int,1> t1; 
       t1.f( 1 ); 
    
       Test<int,2> t2; 
       t2.f( 1,2 ); 
    }
    

    Demo. Wenn noch gemeinsame Member/Basisklassen existieren, einfach in eine gemeinsame Basisklasse auslagern. (Zu dieser Unschönheit gibt es IIRC ein Proposal, dass die Definition des Primärtemplates übernimmt; Wäre klasse, wenn sich EWG das mal anschaut.)

    Sebis Vorschlag ist die zweite (mMn. unschöne) Option.



  • Danke für die Hinweise. Grund für die Frage ist eine alte Array Klasse, die für bis zu 4 Dimensionen verschiedene Funktionen anbietet. Somit gibt es zB für 1D Arrays eine resize( int,int ) Methode, die natürlich Quatsch ist. Um ein Refactoring zu vermeiden wollte ich per enable_if verschiedene Methoden von der Anzahl der Dimensionen abhängig machen.
    Bin jetzt aber Arcoths Vorschlag gefolgt und hab´s per Vererbung umgesetzt. Ist mit Sicherheit die bessere Lösung.


Anmelden zum Antworten