// C++20 - Koncepty (ang. Concepts), autor: Filip Pawlowski
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

// 0. Wstęp
// Standard C++20 wprowadził wiele znaczących zmian do języka. Jednym z obszarów, które zostały rozszerzone są szablony.
// Postaram się opisać podzbiór tej dziedziny, do którego należą Koncepty i funkcjonalności z nimi powiązane.
// Punktem wyjściowym do dalszych rozważań jest zdefiniowanie Konceptu - czyli zestawu wymagań, dotyczących parametrów szablonu, ewaluowany w trakcie kompilacji programu.
// Wyżej wspomniany standardy dodając wsparcie dla nich, dodał też słowa kluczowe takie jak 'concept' i 'requires'.
// Dodatkowo dostarczono zestaw predefiniowanych konceptów z biblioteki standardowej.

// 1. Pierwszy kod Konceptu
// Jako przykładowy przypadek użycia Konceptów przyjęto chęć ograniczenia typów przyjmowanych do szablonu do typów numerycznych.
// Przed wprowadzeniem standardu C++20 zadanie to było jak najbardziej możliwe do wykonania.
// Jednym ze sposobów, na które można było do niego podejść było użycie trait'ów:

#include <type_traits>
template<typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
T add_traits_arg(T number1, T number2) {
    return number1 + number2;
};

// Inna wersja:
template<typename T>
auto add_traits_trailing(T number1, T number2) -> typename std::enable_if<std::is_arithmetic<T>::value, T>::type {
    return number1 + number2;
}

// Jak widać kod działa i poprawnie separuje klasy numeryczne od pozostałych. Warto zauważyć, że char może być również jako taki traktowany w C++.

// Wykorzystanie
void usecase_traits() {
    struct Number {
        int val_ = 1;
    };
    std::cout << "\nadd_traits_arg<int>(1,2):\t\t" << add_traits_arg<int>(1, 2);
    std::cout << "\nadd_traits_trailing<float>(1.0, 2):\t\t" << add_traits_trailing<float>(1.0, 2);
    std::cout << "\nadd_traits_arg<char>('a', 'b'):\t\t" << add_traits_arg<char>('a', 'b');               // Ponieważ char jest konwertowalny do int, jest zaliczany do is_arithmetic
    //    std::cout << add_traits_arg< Number > (Number(1), Number(2)) ;                                        // Nie skompiluje się
    //    std::cout << add_traits_arg< std::string > ("a", "b") ;                                               // Nie skompiluje się
    //    std::cout << add_traits_arg< std::vector<int> > (std::vector<int>({1}), std::vector<int>({2})) ;      // Nie skompiluje się
}

// Ten sam scenariusz używając Konceptów wyglądałby następująco:
#include <concepts>
#include <numeric>

template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;// Koncepty dostępne w bibliotece standardowej

auto add_concepts(Numeric auto a, Numeric auto b) {
    return a + b;
}

// Wykorzystanie
void usecase_concepts() {
    struct Number {
        char val_ = 1;
    };

    std::cout << "\nadd_concepts<int>(1, 2):\t\t" << add_concepts<int>(1, 2);
    std::cout << "\nadd_concepts<float>(1.0, 2):\t\t" << add_concepts<float>(1.0, 2);
    std::cout << "\nadd_concepts<char>('a', 'b'):\t\t" << char(add_concepts<char>('a', 'b'));                                                          // Ponieważ char jest konwertowalny do int, jest zaliczany do is_arithmetic
    //    std::cout << add_concepts< Number > (Number(1), Number(2)) ;                                        // Nie skompiluje się
    //    std::cout << add_concepts< std::string > ("a", "b") ;                                               // Nie skompiluje się
    //    std::cout << add_concepts< std::vector<int> > (std::vector<int>({1}), std::vector<int>({2})) ;      // Nie skompiluje się
}

// Jak widać obydwa podejścia działają, jednakże różnice co do ich jakości są widoczne.
// Zastosowanie Konceptów pozwala znacznie lepiej realizować główne założenia czystego kodowania.
// Czytanie kodu jest bardziej przejrzyste, a zastosowanie i przeznaczenie argumentów wynika z nazw i kontekstu ich umiejscowienia.
// Ponadto zdefiniowawszy taki koncept, możliwe jest używanie go później w wielu miejscach w dogodny sposób, na przykład jako doprecyzowywanie oczekiwanych cech typu auto.
// Wszystko to osiągane jest zachowawszy jeden z kluczowych aspektów programowania z użyciem szablonów - wszystkie operacje i sprawdzenia odbywają się w czasie kompilacji.

// 2. Słowo kluczowe - 'requires'
// Może występować w dwóch formach:
//      - z warunkiem predefiniowanym:
template<typename T>
    requires std::integral<T> || std::floating_point<T>
constexpr double add_concepts_clause(T number1, T number2) {
    return number1 + number2;
}

//      - z wyrażeniem:
template<typename T>
concept Clock = requires(T c) {
                    c.start();
                    c.stop();
                    { c.getTime() } -> std::same_as<int>;
                };
// Taki concept informuje co do wymaganych funkcji klasy - w tym wypadku są to metody: start(), stop(), getTime().
// Możemy również zaaplikować wymaganie co do metody klasy - w tym wypadku wymagamy od metody c.getTiem(), aby zwracany był typ 'int'.

// Analizując takie przykłady można dojść do wniosku, że concepts pozwalają na realizację uszczegóławiania interfejsów klas przekazywanych do szablonów.
// Jest to mechanizm obecny w innych językach takich jak np. Java.

// 3. Koncepty zdefiniowane w nagłówku <concepts>
// same_as
// derived_from
// convertible_to
// common_reference_with
// common_with
// integral
// signed_integral
// unsigned_integral
// floating_point
// assignable_from
// swappable/swappable_with
// destructible
// constructible_from
// default_initializable
// copy_constructible
// move_constructible
// boolean-testable
// equality_comparable/equality_comparable_with
// totally_ordered/totally_ordered_with
// three_way_comparable/three_way_comparable_with
// semiregular
// regular
// movable
// copyable
// invocable/regular_invocable
// predicate
// relation
// equivalence_relation
// strict_weak_order


// 4. Przykładowa aplikacja
int main() {
    usecase_traits();
    usecase_concepts();
    return 0;
}


// Źródła:
// https://en.cppreference.com/w/cpp/language/constraints
// https://developer.apple.com/videos/play/wwdc2022/110367/
// https://www.cppstories.com/2021/concepts-intro/
// https://www.modernescpp.com/index.php/c-core-guidelines-rules-for-the-usage-of-concepts-2
