/*
ZPR - zadanie dodatkowe
Autor: Michał Mokrzycki

Temat: Specyfikatory constexpr i consteval


Contexpr - oznacza, że wartość funkjci lub zmiennej MOŻE (ale nie musi) być
obliczona w czasie kompilacji. Następnie można  używać tak oznaczonych zmiennych
i funkcj w wyrażeniach stałoczasowych, pod warunkiem, że zostały podane
odpowiednie argumenty funkcji.

Conxtexpr użyty w:
- deklaracji obiektu lub niestatycznej funkcji zachoje się jak const
- w deklaracji funkcji lub statetycznego elemntu danych oznacza inline

Uwaga: Jeśli którakolwiek deklaracja funkcji lub szablonu funkcji zawiera
specyfikator constexpr, to każda deklaracja musi zawierać ten specyfikator.

Consteval - może być używane tylko w odniesieniu do funkcji i zmusza do
obliczenia wartości w czasie kompilacji.


*/
/*
Dlaczego tak zależy nam by obliczenia były wykonywane w czasie kompilacji?
- wydajność
- bezpieczeństwo
- czytelność kodu
- możliwość użycia w szablonach

Przykład optymalizacji w Assemblerze:

Bez constexpr:
  int main() {

      int one = 1;
      int ten = 10;
      int sum = one + one + ten;
  }
Daje następujący wynik w assemblerze:
  push    rbp
  mov     rbp, rsp
  mov     DWORD PTR [rbp-4], 1
  mov     DWORD PTR [rbp-8], 10
  mov     eax, DWORD PTR [rbp-4]
  lea     edx, [rax+rax]
  mov     eax, DWORD PTR [rbp-8]
  add     eax, edx
  mov     DWORD PTR [rbp-12], eax
  mov     eax, 0
  pop     rbp
  ret

Natomiast z constexpr:
  int main() {

      constexpr int one = 1;
      constexpr int ten = 10;
      int sum = one + one + ten;
  }
Daje następujący wynik w assemblerze:
  push    rbp
  mov     rbp, rsp
  mov     DWORD PTR [rbp-4], 1
  mov     DWORD PTR [rbp-8], 10
  mov     DWORD PTR [rbp-12], 12
  mov     eax, 0
  pop     rbp
  ret

Łatwo zauważyć, że w drugim przypadku nie ma operacji dodawania, linia
  mov     DWORD PTR [rbp-12], 12
zastępuje nam aż 4 instrukcje z pierwszego przypadku.
Wartość sumy jest obliczana w czasie kompilacji, a nie w czasie wykonywania co pozwala zaoszczędzić cykle procesora.
*/
#include <iostream>

// Przykłady:
// constexpr obliczanie silni. Można obliczac w czasie kompilacji jak i w czasie
// wykonywania
constexpr int factorial(int n) { return (n <= 1) ? 1 : (n * factorial(n - 1)); }

// consteval obliczanie ciągu Fibonacciego. Musi być obliczane w czasie
// kompilacji

consteval int fibonacci(int n)
{
  return (n <= 1) ? n : (fibonacci(n - 1) + fibonacci(n - 2));
}

// egzekwujemy obliczenia w czasie kompilacji za pomocą consteval
consteval unsigned combination(unsigned m, unsigned n)
{
  return factorial(n) / factorial(m) / factorial(n - m);
}

int main(int argc, const char *[])
{
  // Przykłady użycia

  // Użycie funkjci constexpr, obliczanie w czasie kompilacji
  constexpr int fact = factorial(5);
  std::cout << "Factorial of 5 is: " << fact << std::endl;

  // można też obliczać w czasie wykonywania, używamy volatile by wykluczyć optymalizacje
  volatile int fact_runtime = factorial(5);
  std::cout << "Factorial of 5 is: " << fact_runtime << std::endl;

  // Użycie consteval, obliczanie w czasie kompilacji,
  constexpr int fib = fibonacci(5);
  std::cout << "Fibonacci of 5 is: " << fib << std::endl;

  // poniższy przykład powodowałby błąd kompilacji, ponieważ consteval musi być
  // obliczane w czasie kompilacji, nie wykonania
  // int runtime_fib = fibonacci(5);
  unsigned y = factorial(argc); // OK, obliczane w czasie wykonania
  std::cout << "Factorial of " << argc << " is: " << y << std::endl;
  unsigned x = combination(2, 5); // OK, obliczane w czasie kompilacji 2 i 5 są stałymi wyrażeniami
  std::cout << "Combination of 2 and 5 is: " << x << std::endl;

  // poniższa linijka powodowałaby błąd
  // unsigned z = combination(argc, 7); // 'argc' nie jest wyrażeniem stałym, bo argumenty są znane dopiero w czasie wykonania

  return 0;
}
/*
Zmiany constexpr na przestrzeni kolejnych wersji C++:
C++11 - wprowadzenie constexpr, podstawowa funkcjonalność, funkcje mogły
zawierać tylko jedną instrukcję return

C++14 - constexpr funkcje mogły zawierać
więcej instrukcji 'return' oraz inne jak np. pętle czy warunki
Przykład: */

constexpr int factorial_14(int n)
{
  int result = 1;
  for (int i = 1; i <= n; ++i)
  {
    result *= i;
  }
  return result;
}
// C++17 - można definiować lamdy jako constexpr, constexpr dla zmiennych
// statycznych:
constexpr auto add17 = [](int a, int b)
{ return a + b; };

/* C++20
- Wyrażenie constexpr mozę być używane z wirtualnymi funkcjami
- funkcje z nim związane mogą zawierać wyrażenia try/catch
- możliwość używania new i delete w kontekście constexpr
- Destruktory mogą być teraz constexpr (z pewnymi wyjątkami, między innymi nie
mogły zawierać trzech wyżej wymienionych konstrukcji)
Przykład:*/
constexpr int safe_divide(int a, int b)
{
  if (b == 0)
    throw std::runtime_error("Division by zero");
  return a / b;
}
/*
C++23
- zniesione praktycznie wszystkie ograniczenia związane z constexpr

Wciąż jednak istnieje wiele powodów, z powodu których constexpr nie może być
ustawiany domyślnie.
*/
/*Linki do dokumentacji i wykorzystanych materiałów:
    https://en.cppreference.com/w/cpp/language/consteval
    https://en.cppreference.com/w/cpp/language/constexpr
    https://www.cppstories.com/2022/const-options-cpp20/
    https://medium.com/@myronphan/c-compile-time-evaluation-versus-run-time-evaluation-13c000b7f4a1
*/
// wymagana kompliacja w trybie C++20, g++ -std=c++20 conste-val-xpr.cpp
// wywołanie ./a.out arg1 arg2 ...