#include <iostream>
#include <print>
#include <cstring>

// Allgemeines Template zum Abspeichern zweier Daten
template <typename T1, typename T2>
class Pair
{
    T1 data1;   // Datum 1
    T2 data2;   // Datum 2
public:
    // ctor
    Pair(const T1& d1, const T2& d2) : data1(d1), data2(d2)
    {}
    // Ueberladener Operator << zur Ausgabe
    // Muss ein Funktionstemplate sein, da die Datentype von Pair variieren!
    template<typename U1, typename U2>
    friend std::ostream& operator << (std::ostream& os, const Pair<U1, U2>& obj);
};
// Funktionstemplate zur Ausgabe des allg. Templates
template <typename T1, typename T2>
std::ostream& operator << (std::ostream& os, const Pair<T1, T2>& obj)
{
    //os << std::format("Datum1: {}, Datum2: {}\n", obj.data1, obj.data2);
    std::println(os,"Datum1: {}, Datum2: {}", obj.data1, obj.data2);
    return os;
}
// Spezialsiertes Template fuer den Fall, dass der zweite
// Templateparameter ein char-Zeiger ist
template <typename T1>
class Pair<T1, const char*>
{
    T1      data1;      // Datum 1
    char*   pdata2;     // Zeiger auf abgelegten C-String
public:
    // ctor
    Pair(const T1& d1, const char* pd2) : data1(d1)
    {
        // Speicher zur Ablage des Textes definieren
        auto len = strlen(pd2) + 1;
        pdata2 = new char[len];
        strcpy_s(pdata2, len, pd2);
    }
    // dtor, gibt den im ctor reservierten Speicher frei
    ~Pair()
    {
        delete[] pdata2;
    }
    // Ueberladener Operator << zur Ausgabe
    template<typename U1>
    friend std::ostream& operator << (std::ostream& os, Pair<U1, const char*>& obj);
};
// Funktionstemplate zur Ausgabe des Template mit T2=char*
template <typename T1>
std::ostream& operator << (std::ostream& os, Pair<T1, const char*>& obj)
{
    //os << std::format("Datum1: {}, Datum2: {}\n", obj.data1, obj.pdata2);
    std::println(os,"Datum1: {}, Datum2: {}", obj.data1, obj.pdata2);
    return os;
}
