2 maja 2013

constexpr (klasa będąca literałem)

Jeżeli chcemy stworzyć obiekt constexpr, konstruktor klasy także będzie musiał być constexpr. Klasa będzie wtedy literałem. Taki konstruktor będzie zazwyczaj pusty i będzie musiał zainicjować wszystkie zmienne na liście inicjalizacyjnej.

constexpr nie gwarantuje, że kod zostanie wykonany podczas kompilacji. Można się jednak co do tego upewnić stosując static_assert, które to niejako wymusza. Ciekawostka, w C++20 zostanie wprowadzone nowe słowo kluczowe consteval, które gwarantuje że funkcja zostanie wygenerowana podczas kompilacji (przykład na samym końcu).
#include <iostream>

using namespace std;

class MyClass {
public:
    constexpr MyClass(int arg) : a{arg}, b{0} {
        b = 4;
    }

    int a;
    int b;
};

int main()
{
    constexpr MyClass cobj{3};
    static_assert(cobj.a == 3 and cobj.b == 4, "Doesn't match at compile time");

    cout << cobj.a << " " << cobj.b << endl;
}
Wynik:
3 4
constexpr dodaje implicit const do deklarowanych obiektów (w C++11 dodawał on również const do funkcji, jednak w C++14 z tego zrezygnowano). Jeżeli więc chcielibyśmy rozbudować klasę o metodę, która pozwala na modyfikowanie pól, kompilacja zakończy się niepowodzeniem.
#include <iostream>

using namespace std;


class MyClass {
public:
   constexpr MyClass(int i) : val(i) {}
   constexpr void set(int num) {
       val = num;
   }
   constexpr int get() const {
       return val;
   }

   int val;
};

int main()
{
    constexpr MyClass cobj{4};

//  cobj.set(44);               // Error - shake powinno być const
    constexpr int v = cobj.get();
    static_assert(v == 4, "Doesn't match at compile time");
    cout << v << endl;

    return 0;
}
Wynik:
4
Jedyny znany mi sposób na ominięcie tego problemu, to stworzenie nowej funkcji (także constexpr). Deklarowane w niej zmienne domyślnie będę constexpr, więc nie trzeba explicit podawać tego specyfikatora, nie zostanie dodany const, a więc będzie można zawołać metodę set() bez żadnego problemu.
#include <iostream>

using namespace std;


class MyClass {
public:
   constexpr MyClass(int i) : val(i) {}
   constexpr void set(int num) {
       val = num;
   }
   constexpr int get() const {
       return val;
   }

   int val;
};

constexpr MyClass change() {
   MyClass cst{4};
   cst.set(56);
   return cst;
}

int main()
{
   constexpr MyClass cst = change();
   constexpr int v = cst.get();
   static_assert(v == 56, "Doesn't match at compile time");
   cout << v << endl;

   return 0;
}
Wynik
56
Wymuszenie generowania funkcji podczas procesu kompilacji (C++20), za pomocą consteval.
#include <iostream>

using namespace std;


class MyClass {
public:
   constexpr MyClass(int i) : val(i) {}
   constexpr void set(int num) {
       val = num;
   }
   constexpr int get() const {
       return val;
   }

   int val;
};

consteval MyClass change() {
   MyClass cst{4};
   cst.set(56);
   return cst;
}

int main()
{
   constexpr MyClass cst = change();
   constexpr int v = cst.get();
   static_assert(v == 56, "Doesn't match at compile time");
   cout << v << endl;

   return 0;
}

W tej chwili działa tylko z clang-iem (wersja 9.0.0-2):
$ clang++ -std=c++2a main.cpp
$ ./a.out
56

Brak komentarzy:

Prześlij komentarz