Compiler Konstanten berechnen lassen


  • Mod

    Illuminatus schrieb:

    Das leuchtet mir jetzt nicht ein. Wenn "const summe = a + b" funktioniert, warum funktioniert dann mein Ausdruck nicht? Warum ist mein Ausdruck unbedingt eine Compilezeitkonstante?

    Weil dein generateK() eine Funktion ist, const Rückgabewert hin oder her. Der Compiler kann/darf nicht in die Funktion gucken, ob der Ausdruck dort eine Compilezeitkonstante ist. Das const am Rückgabewert macht gar nichts. Folgendes ist absolut legal:

    const int nicht_konstant()
    {
     return rand();
    }
    

    Um trotzdem so etwas wie

    int konstant()
    {
     return 5;
    }
    
    // ...
    const int = konstant(); // Geht nicht!
    

    zu ermöglichen ist das schon erwähnte constexpr eingeführt worden:

    constexpr int konstant()
    {
     return 5;
    }
    
    // ...
    const int = konstant(); // Geht auf den neuesten Compilern.
    

    Im Zusammenspiel mit Arrays dürfte das jedoch etwas schwierig sein, wegen der ungewöhnlichen Rückgabesemantik. Möglich, dass es irgendwelche Tricks dafür gibt.


  • Mod

    Könnte z.B. so aussehen

    #include <cstdint>
    #include <cmath>
    ///////////////////////
    // Ein paar Hilfstemplates um nicht alle Indizes aufzählen zu müssen
    template <std::size_t... i> struct indexes
    {
        typedef indexes type;
    };
    
    template <typename T, typename U> struct concat
        : concat<typename T::type, typename U::type> {};
    template <std::size_t... i, std::size_t...j> struct concat<indexes<i...>, indexes<j...>>
        : indexes<i..., ( j + sizeof... i )...> {};
    
    template <typename T> struct twice
        : concat<T, T> {};
    
    template <std::size_t N> struct make_indexes
        : concat<twice<make_indexes<N / 2>>, make_indexes<N % 2>> {};
    template <> struct make_indexes<1>
        : indexes<0> {};
    template <> struct make_indexes<0>
        : indexes<> {};
    ///////////////////////////////////////////
    
    constexpr std::uint32_t compute_md5_element(std::uint32_t i)
    {
        return std::fabs( std::sin( i + 1 ) ) * 4294967296.0;
    }
    
    template <typename = make_indexes<64>::type> struct md5_table;
    template <std::size_t... i> struct md5_table<indexes<i...>>
    {
        static const std::uint32_t data[sizeof... i];
    };
    template <std::size_t... i>
    const std::uint32_t md5_table<indexes<i...>>::data[sizeof... i] = { compute_md5_element( i )... };
    
    // Eigentlich:
    //template <std::size_t... i> struct md5_table<indexes<i...>>
    //{
    //    static constexpr std::uint32_t data[] = { compute_md5_element( i )... };
    //};
    // Aber gcc kommt mit dieser Syntax noch nicht zurecht
    

    Das gewünschte Ergebnis ist dann einfach md5_table<>::data



  • SeppJ schrieb:

    Weil dein generateK() eine Funktion ist, const Rückgabewert hin oder her. Der Compiler kann/darf nicht in die Funktion gucken, ob der Ausdruck dort eine Compilezeitkonstante ist. Das const am Rückgabewert macht gar nichts.

    Warum funktioniert das ganze dann aber wenn ich die Definition außerhalb der Klasse vornehme?


  • Mod

    Illuminatus schrieb:

    SeppJ schrieb:

    Weil dein generateK() eine Funktion ist, const Rückgabewert hin oder her. Der Compiler kann/darf nicht in die Funktion gucken, ob der Ausdruck dort eine Compilezeitkonstante ist. Das const am Rückgabewert macht gar nichts.

    Warum funktioniert das ganze dann aber wenn ich die Definition außerhalb der Klasse vornehme?

    Ohne Code gibts nur Glaskugelraten.



  • class MD5
    {
    private:
    static const unsigned int * k;
    }
    
    const unsigned int * MD5::k = generateK();
    

  • Mod

    So wie du es jetzt schreibst, ist das eine Laufzeitkonstante die zum Programmstart ausgewertet wird:

    #include <iostream>
    
    const unsigned int * generateK()
    {
      std::cout<<"Dies passiert erst zur Laufzeit\n";
      return 0;
    }
    
    class MD5
    {
    private:
      static const unsigned int * k;
    };
    
    const unsigned int * MD5::k = generateK();
    
    int main(){}
    

    Wird so vielleicht noch deutlicher:

    const unsigned int generateK();
    
    class MD5
    {
    public:
      static const unsigned int k;
    };
    
    const unsigned int MD5::k = generateK();
    
    int main()
    {
      int foo[MD5::k]; // Nur mit speziellen Compilererweiterungen für VLAs möglich
    }
    


  • camper ist ein kranker Freak. Vielleicht kann der Freak ja seinen Code erklären? 😕



  • Ok, das heißt, definitionen "in-class" werden zur Compilezeit bearbeitet, Definitionen außerhalb aber erst zur Laufzeit?

    Eine andere Frage: Warum funktioniert mein Code nur mit einem Pointer, nicht aber mit k als Array?


  • Mod

    Illuminatus schrieb:

    Ok, das heißt, definitionen "in-class" werden zur Compilezeit bearbeitet, Definitionen außerhalb aber erst zur Laufzeit?

    Zuerst einmal ein bisschen Begriffsklärung:

    // irgenwelche Typen T, U
    class Foo
    {
        static T x; // Deklaration
        U y;        // Definition
    };
    
    T Foo::x; // Definition
    

    Es ist möglich, den Membern bereits in der Klassendefinition einen Initialisierer mtizugeben. Dieser Initialisierer muss ein konstanter Ausdruck sein. Dieser Initialisierer macht aus der Deklaration keine Definition.
    In C++98 ist das allerdings auf statische konstante integrale Member beschränkt. Eine seperate Definition (dann ohne Initialisierer) ist ggf. trotzdem erforderlich.
    In C++0x ist das nicht mehr auf integrale Typen beschränkt, jetzt muss es ein literal Typ sein; erfolgt die Initialisierung mittels Initialisierungsliste, muss dabei jeder einzelne Initialisierer ein konstanter Ausdruck sein. (Anm. gcc beherrscht die Initialisierung in der Klasse mittels geschweifter Klammern noch nicht, deshalb funktioniert das Ganze gegenwärtig noch nicht mit Arrays).

    Erfolgt die Initialisierung außerhalb der Klasse, muss der Initialisierer kein konstanter Ausruck sein, in diesem Falle ist dann aber das initialisierte Objekt nicht in konstanten Ausdrücken verwendbar.


  • Mod

    Und noch zur zweiten Frage: Arrays haben in C und C++ ein ganz anderes Verhalten als die anderen Basisdatentypen:

    int *P;
    int foo[3];
    int bar[3];
    bar = foo; // Geht nicht, Arrays kann man nicht zuweisen
    bar = p;   // Geht erst recht nicht, ein Array ist kein Pointer!
    p = foo;   // Das ist ok, p ist nun ein Zeiger auf das erste Element von foo (Stichwort: Array to Pointer decay)
    


  • Aah ok, jetzt weiß ich Bescheid, danke! 🙂

    EDIT: Das VErhalten von Arrays kenne ich, ich wusste nur nicht, warum sich diese nur über geschweifte Klammern initialisieren lassen.


Anmelden zum Antworten