Konstruktor für spezielle Typen "Umleiten"?



  • Ich hab folgendes Problem:
    Ich hab eine Klasse mit zwei verschiedenen Kontruktoren, einer davon soll zwei verschiedene integrale Parameter haben, der andere ein Iteratorpaar als Parameter. Die Deklaratoinen sähen also in etwa wie folgt aus:

    class C
    {
    public:
      C(int i, long l);
    
      template <class Iter>
      C(Iter first, Iter last);
    };
    

    Nun möchte ich aber, wenn zwei int als Parameter übergeben werden, der ersten Konstruktor aufrufen lassen. Wie bekomm ich das ohne große Codeduplizierung und ohne große Abfragen im zweiten Ctor hin?



  • Wenn zwei int übergeben werden sollte der 1. Konstruktor aufgerufen werden und ein int in long gecastet werden, sofern der Konstruktor nicht explicit ist


  • Administrator

    söfgjsä schrieb:

    Wenn zwei int übergeben werden sollte der 1. Konstruktor aufgerufen werden und ein int in long gecastet werden, sofern der Konstruktor nicht explicit ist

    Nein, dass geht nicht.

    @pumuckl,
    Mach doch einen zusätzlichen Konstruktor, welcher zwei int Typen entgegen nimmt.

    Grüssli



  • Hm. Ginge das nicht mit boost::enable_if?



  • hustbaer schrieb:

    Hm. Ginge das nicht mit boost::enable_if?

    hatte ich mit enable_if nicht immer den rückgabetyp maniputtgemacht? sowas hat der konstruktor leider nicht. ich könnte ein drittes pseudoargument puttmachen. naja, irgendwie nicht lecker.



  • Aber das funktioniert doch Oo

    class C
    {
    public:
      C(int i, long l){}
    
      template <class Iter>
      C(Iter first, Iter last){}
    };
    
    int main()
    {
        int a, b;
        C c(a,b); //funktioniert
    }
    

    Oder verstehe ich das Problem falsch 😕



  • Ist explicit machen und auf long casten keine Option? Oder einen Konstruktor int,int der ein Objekt erstellt und dann nen swap macht?


  • Administrator

    söfgjsä schrieb:

    Oder verstehe ich das Problem falsch 😕

    Ja oder du merkst nicht, dass es die falsche Version aufruft. Es ruft nämlich die Templateversion auf. Mach mal das:

    #include <iostream>
    
    class C
    {
    public:
      C(int i, long l)
      {
        std::cout << "int i, long l" << std::endl;
      }
    
      template <class Iter>
      C(Iter first, Iter last)
      {
        std::cout << "Iter first, Iter last" << std::endl;
      }
    };
    
    int main()
    {
      int a, b;
      C c(a,b); // Gibt garantiert "Iter first, Iter last" aus.
      // Und das will er ja gerade nicht ;)
    }
    

    @hustbaer,
    Und wie stellst du dir das vor? 🙂
    Es gibt nicht mal eine eindeutige Möglichkeit einen Iterator zu erkennen ...

    Ich bin immer noch für einen Konstruktor mit int, int . Um doppelten Intialisierungslisten Code zu verhindern, kann man die Member intern in ein struct verpacken. Zudem kann man eine private init Funktion verwenden. Also kurz skizziert:

    class C
    {
    private:
      struct
      {
        int m_i;
        long m_l;
    
        Members(int i, long l)
          : m_i(i)
          , m_l(l)
        {
        }
      }
      m_members;
    
    public:
      C(int i, long l)
        : m_members(i, l)
      {
        init();
      }
    
      C(int i, int b)
        : m_members(i, b)
      {
        init();
      }
    
      template <class Iter>
      C(Iter first, Iter last)
        : m_members(0, 0)
      {
        // whatever ...
      }
    
    private:
      void init()
      {
        // whatever ...
      }
    };
    

    Kommt natürlich ganz darauf an, wie die Klasse intern aussieht.

    Grüssli



  • hustbaer schrieb:

    Hm. Ginge das nicht mit boost::enable_if?

    Ja, an SFINAE hatte ich auch gedacht. Es fehlt nur noch eine Abfrage, ob "Iter" wirklich ein Iterator ist.

    class A
    {
    public:
      A(int,long);
    
      template<typename Iter>
      A(Iter beg, Iter end,
        typename boost::enable_if<is_iterator<Iter>,char>::type=0)
      {
        //...
      }
    };
    

    Leider fällt mir spontan nichts für is_iterator ein. Mit Concepts wär das einfach gewesen (requires InputIterator<Iter> ).

    Gruß,
    SP



  • Nachtrag:
    Es gibt eine Diskussion is_iterator in der Boost Mailingliste. Es sieht nicht so aus, als ob es in C++03 möglich wäre.

    Gruß,
    SP



  • SFINAE in C++0x mit Default-Template-Argumenten für Funktionen und decltype:

    template<typename T>
    struct make
    {
      static T& lvalue();
      static T&& rvalue();
    };
    
    class A
    {
    public:
      A(int,long);
    
      template< typename Iter, // Dereferenzieren möglich?
                typename Test = decltype(*make<Iter>::rvalue()) >
      //                                 ^
      A(Iter beg, Iter end)
      {
        // ...
      }
    };
    

    müsste nach meinem Verständnis in C++0x funktionieren. Diese oder ähnliche Varianten habe ich aber unter g++ 4.3 mit -std=c++0x nicht zum Laufen bekommen, da der Compiler es vorzieht, eine Fehlermeldung auszuspucken, statt das Template einfach zu ignorieren falls das Einsetzen schiefgeht. Ihr könnt ja mal mit g++ 4.4 testen -- der soll, was SFINAE angeht, gnädiger sein.

    Mit diesen "erweiterten SFINAE Möglichkeiten" wären wir zumindest nicht ganz verloren ... so ohne Concepts. Die ganzen syntaktischen Typ-Bedingungen sollten per decltype testbar sein. 🙂

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    template<typename Iter>
      A(Iter beg, Iter end,
        typename boost::enable_if<is_iterator<Iter>,char>::type=0)
    

    Guter Ansatz, ich werds mal mit mit boost::disable_if<is_integral<Iter>,char>::type = 0 versuchen wenn ich das nächstemal dran sitze 🙂



  • pumuckl schrieb:

    Guter Ansatz, ich werds mal mit mit boost::disable_if<is_integral<Iter>,char>::type = 0 versuchen wenn ich das nächstemal dran sitze 🙂

    So geht's natürlich auch! 🙂
    Oder vielleicht sogar mit is_convertible<Iter,int> und is_convertible<Iter,long> .

    Ich habe übrigends vorhin gcc-4.4.1 kompiliert, um das C++0x-Beispiel mit decltype zu testen: Es funzt. 🙂

    Gruß,
    SP



  • War leider nichts - er versucht krampfhaft die Iteratorversion aufzurufen bei zwei ints und stolpert über enable_if::type 😞


  • Administrator

    pumuckl schrieb:

    War leider nichts - er versucht krampfhaft die Iteratorversion aufzurufen bei zwei ints und stolpert über enable_if::type 😞

    Kein Wunder, es ist ja auch kein Substitutionsfehler. Die Substitution klappt einwandfrei. Grundsätzlich müsstest du sowas machen:

    template<typename IterT>
    C(IterT, IterT, typename std::iterator_traits<IterT>::iterator_category category = typename std::iterator_traits<IterT>::iterator_category())
    {
    }
    

    Nur dass es problematisch ist, weil es unter MSVC zum Beispiel auch Spezialisierungen für std::iterator_traits für int , double , float , long , short , usw. gibt. Daher funktioniert dies auch nicht ... Es gibt eben keine Möglichkeit einen Iterator zu erkennen.

    Das einzige was du machen könntest, sind gewisse Typen explizit auszuschliessen. Ehm, das müsste dann so gehen:

    struct EmptyType { };
    
    temlate<typename T>
    struct ExcludedTraits
    {
      typename EmptyType Allowed;
    };
    
    template<>
    struct ExcludedTraits<int>
    {
      // int ausschliessen.
    };
    
    class C
    {
    public:
      C(int, long) { std::cout << "funzt" << std::endl; }
    
      template<typename IterT>
      C(IterT, IterT, 
         typename ExcludedTraits<IterT>::Allowed = typename ExcludedTraits<IterT>::Allowed())
      {
        std::cout << "meep" << std::endl;
      }
    };
    
    int main()
    {
      int a = 0;
      int b = 0;
    
      C c(a, b); // sollte gehen ...
    }
    

    Vielleicht kann man es über Typlisten noch ein wenig vereinfachen.
    Aber ob das viel einfacher ist als einfach einen zusätzlichen Konstruktor? 🙂

    Grüssli



  • pumuckl schrieb:

    War leider nichts - er versucht krampfhaft die Iteratorversion aufzurufen bei zwei ints und stolpert über enable_if::type 😞

    Wie wär's mit etwas Code? Ich habe vorhin das hier probiert:

    #include <iostream>
    #include <boost/utility/enable_if.hpp>
    #include <boost/type_traits/is_integral.hpp>
    
    void foo(int, long)
    {
    	std::cout << "1\n";
    }
    
    template<typename Iter>
    void foo(Iter beg, Iter end,
    	typename boost::disable_if<boost::is_integral<Iter>,char>::type=0)
    {
    	std::cout << "2\n";
    }
    
    int main()
    {
    	foo(23,42);
    }
    

    und es macht genau das, was es soll ("1" ausgeben).

    Gruß,
    SP



  • Hätte ein normaler User so eine Frage gestellt, wäre schon längst die Frage gekommen: Was willst du eigentlich konkret damit erreichen? Da du aber sicher weiß, was du willst, ist die Farge wohl überflüssig. Mich interessiert aber schon, warum man einen Konstruktor benötigt, der ein int und ein long als Parameter hat. Schließlich gilt immer sizeof(int) <= sizeof(long) , also könntest du auch zwei long als Parameter nehmen.

    Gruß
    Don06


  • Administrator

    Oh Gott, ich trottel ... klar funktioniert dies! Ich habe boost::enable_if<cond, char> mit Select<cond, char, void> verwechselt.

    Ich ziehe mein vorheriges Posting zurück ... peinlich, peinlich ...

    Grüssli



  • Ich bin dabei ein Containertemplate zu schreiben. Wie bei std::vector solls einen Ctor für ein iterator-Range geben, sowie einen für size_type und T. letzterem kann man bei std::vector für T=int oder T=long usw. zwei int übergeben, bzw. sollte man. In der Standardbibliothek die ich hier habe ist es so gelöst, dass der Range-Ctor bei Iter=int sich so verhält wie der NxT-Ctor. Ich wollte nach eier Lösung suchen wo gleich der richtige Ctor gewählt wird.

    Mein Ansatz bisher:

    template <class T>
    class MyContainer /*: boost::equality_comparable<MyContainer<T> >*/
    {
      template <class Iter>
      struct DisableInt
      {
        typedef typename boost::disable_if<
          boost::is_integral<Iter>, char
        >::type type;
      };
    
    public:
    
      MyContainer();
      explicit MyContainer( size_type n, const_value_type& value= T());
      template <class InputIterator >
      MyContainer(InputIterator first, InputIterator last, 
             typename DisableInt<InputIterator>::type = 0);
    };
    

    Beim Compilieren gibts dann entsprechend eine Fehlermeldung:

    c:\development\projects\lib_pumu\src\pumu\container\mycontainer.hpp(26) : error C2039: 'type' : is not a member of 'boost::disable_if<Cond,T>'
    1>        with
    1>        [
    1>            Cond=boost::is_integral<int>,
    1>            T=char
    1>        ]
    1>        c:\development\projects\lib_pumu\test\pumu\container\testmycontainer.cpp(81) : see reference to class template instantiation 'pumu::container::MyContainer<T>::DisableInt<Iter>' being compiled
    1>        with
    1>        [
    1>            T=int,
    1>            Iter=int
    1>        ]
    

  • Administrator

    Wirf das struct DisableInt weg. So wird SFINAE nicht funktionieren. Der Substitutionsfehler passiert nicht im Funktionskopf, sondern in der Struktur DisableInt . Der Fehler muss aber im Funktionskopf passieren, damit es funktioniert.

    Grüssli



  • Ja hatte da n Denkfehler. Hiermit gehts:

    template <class Iter>
    struct DisableInt : public boost::disable_if<boost::is_integral<Iter>, char>
    {};
    

Anmelden zum Antworten