Literały definiowane przez użytkownika

Istnieją sześć głównych kategorii literałów w języku C++: liczba całkowita, znak, zmiennoprzecinkowy, ciąg, wartość logiczna i wskaźnik. Począwszy od języka C++11, można zdefiniować własne literały na podstawie tych kategorii, aby zapewnić skróty składniowe dla typowych idiomów i zwiększyć bezpieczeństwo typów. Załóżmy na przykład, że masz klasę Distance . Możesz zdefiniować literał dla kilometrów i drugi dla mil i zachęcić użytkownika do jawnego definiowania jednostek miary, pisząc: auto d = 42.0_km lub auto d = 42.0_mi. Brak zalet wydajności ani wad literałów zdefiniowanych przez użytkownika; są przeznaczone głównie dla wygody lub dla potrącenia typu czasu kompilacji. Biblioteka Standardowa zawiera literały zdefiniowane przez użytkownika dla std::stringelementów , dla std::complex, oraz dla jednostek w operacjach czasu i czasu trwania w nagłówku <chrono> :

Distance d = 36.0_mi + 42.0_km;         // Custom UDL (see below)
std::string str = "hello"s + "World"s;  // Standard Library <string> UDL
complex<double> num =
   (2.0 + 3.01i) * (5.0 + 4.3i);        // Standard Library <complex> UDL
auto duration = 15ms + 42h;             // Standard Library <chrono> UDLs

Podpisy operatorów literału zdefiniowanego przez użytkownika

Literał zdefiniowany przez użytkownika jest implementowany przez zdefiniowanie operatora "" w zakresie przestrzeni nazw przy użyciu jednej z następujących formularzy:

ReturnType operator "" _a(unsigned long long int);   // Literal operator for user-defined INTEGRAL literal
ReturnType operator "" _b(long double);              // Literal operator for user-defined FLOATING literal
ReturnType operator "" _c(char);                     // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _d(wchar_t);                  // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _e(char16_t);                 // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _f(char32_t);                 // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _g(const char*, size_t);      // Literal operator for user-defined STRING literal
ReturnType operator "" _h(const wchar_t*, size_t);   // Literal operator for user-defined STRING literal
ReturnType operator "" _i(const char16_t*, size_t);  // Literal operator for user-defined STRING literal
ReturnType operator "" _g(const char32_t*, size_t);  // Literal operator for user-defined STRING literal
ReturnType operator "" _r(const char*);              // Raw literal operator
template<char...> ReturnType operator "" _t();       // Literal operator template

Nazwy operatorów w poprzednim przykładzie są symbolami zastępczymi dla dowolnej podanej nazwy; jednak wymagane jest podkreślenie wiodące. (Tylko biblioteka Standardowa może definiować literały bez podkreślenia). Zwracany typ to miejsce, w którym można dostosować konwersję lub inne operacje wykonywane przez literał. Ponadto dowolny z tych operatorów można zdefiniować jako constexpr.

Literały gotowane

W kodzie źródłowym dowolny literał, niezależnie od tego, czy jest zdefiniowany przez użytkownika, czy nie, jest zasadniczo sekwencją znaków alfanumerycznych, takich jak 101, lub 54.7, lub "hello"true. Kompilator interpretuje sekwencję jako ciąg liczb całkowitych, zmiennoprzecinkowych, const char* itd. Literał zdefiniowany przez użytkownika, który przyjmuje jako dane wejściowe niezależnie od typu kompilatora przypisanego do wartości literału, jest nieformalnie znany jako literał gotowany. Wszystkie powyższe operatory z wyjątkiem _r i _t są gotowane literały. Na przykład literał 42.0_km będzie wiązać się z operatorem o nazwie _km, który miał podpis podobny do _b, a literał 42_km wiązałby się z operatorem z podpisem podobnym do _a.

W poniższym przykładzie pokazano, jak literały zdefiniowane przez użytkownika mogą zachęcić osoby wywołujące do jawnego wprowadzania danych wejściowych. Aby utworzyć element Distance, użytkownik musi jawnie określić kilometry lub mile przy użyciu odpowiedniego literału zdefiniowanego przez użytkownika. Ten sam wynik można osiągnąć na inny sposób, ale literały zdefiniowane przez użytkownika są mniej pełne niż alternatywy.

// UDL_Distance.cpp

#include <iostream>
#include <string>

struct Distance
{
private:
    explicit Distance(long double val) : kilometers(val)
    {}

    friend Distance operator"" _km(long double val);
    friend Distance operator"" _mi(long double val);

    long double kilometers{ 0 };
public:
    const static long double km_per_mile;
    long double get_kilometers() { return kilometers; }

    Distance operator+(Distance other)
    {
        return Distance(get_kilometers() + other.get_kilometers());
    }
};

const long double Distance::km_per_mile = 1.609344L;

Distance operator"" _km(long double val)
{
    return Distance(val);
}

Distance operator"" _mi(long double val)
{
    return Distance(val * Distance::km_per_mile);
}

int main()
{
    // Must have a decimal point to bind to the operator we defined!
    Distance d{ 402.0_km }; // construct using kilometers
    std::cout << "Kilometers in d: " << d.get_kilometers() << std::endl; // 402

    Distance d2{ 402.0_mi }; // construct using miles
    std::cout << "Kilometers in d2: " << d2.get_kilometers() << std::endl;  //646.956

    // add distances constructed with different units
    Distance d3 = 36.0_mi + 42.0_km;
    std::cout << "d3 value = " << d3.get_kilometers() << std::endl; // 99.9364

    // Distance d4(90.0); // error constructor not accessible

    std::string s;
    std::getline(std::cin, s);
    return 0;
}

Liczba literału musi używać liczby dziesiętnej. W przeciwnym razie liczba zostanie zinterpretowana jako liczba całkowita, a typ nie będzie zgodny z operatorem. W przypadku danych wejściowych zmiennoprzecinkowych typ musi mieć long doublewartość , a dla typów całkowitych musi mieć wartość long long.

Nieprzetworzone literały

W nieprzetworzonym literału zdefiniowanym przez użytkownika operator akceptuje literał jako sekwencję wartości znaków. Do ciebie należy interpretowanie tej sekwencji jako liczby lub ciągu lub innego typu. Na liście operatorów pokazanych wcześniej na tej stronie _r i _t może służyć do definiowania nieprzetworzonych literałów:

ReturnType operator "" _r(const char*);              // Raw literal operator
template<char...> ReturnType operator "" _t();       // Literal operator template

Można użyć nieprzetworzonych literałów, aby zapewnić niestandardową interpretację sekwencji danych wejściowych, która różni się od normalnego zachowania kompilatora. Można na przykład zdefiniować literał, który konwertuje sekwencję 4.75987 na niestandardowy typ dziesiętny zamiast typu zmiennoprzecinkowego IEEE 754. Nieprzetworzone literały, takie jak literały gotowane, mogą być również używane do sprawdzania poprawności sekwencji wejściowych w czasie kompilacji.

Przykład: ograniczenia nieprzetworzonych literałów

Nieprzetworzone operatory literału i operatora literału działają tylko dla literałów zdefiniowanych przez użytkownika całkowitych i zmiennoprzecinkowych, jak pokazano w poniższym przykładzie:

#include <cstddef>
#include <cstdio>

// Literal operator for user-defined INTEGRAL literal
void operator "" _dump(unsigned long long int lit)
{
    printf("operator \"\" _dump(unsigned long long int) : ===>%llu<===\n", lit);
};

// Literal operator for user-defined FLOATING literal
void operator "" _dump(long double lit)
{
    printf("operator \"\" _dump(long double)            : ===>%Lf<===\n",  lit);
};

// Literal operator for user-defined CHARACTER literal
void operator "" _dump(char lit)
{
    printf("operator \"\" _dump(char)                   : ===>%c<===\n",   lit);
};

void operator "" _dump(wchar_t lit)
{
    printf("operator \"\" _dump(wchar_t)                : ===>%d<===\n",   lit);
};

void operator "" _dump(char16_t lit)
{
    printf("operator \"\" _dump(char16_t)               : ===>%d<===\n",   lit);
};

void operator "" _dump(char32_t lit)
{
    printf("operator \"\" _dump(char32_t)               : ===>%d<===\n",   lit);
};

// Literal operator for user-defined STRING literal
void operator "" _dump(const     char* lit, size_t)
{
    printf("operator \"\" _dump(const     char*, size_t): ===>%s<===\n",   lit);
};

void operator "" _dump(const  wchar_t* lit, size_t)
{
    printf("operator \"\" _dump(const  wchar_t*, size_t): ===>%ls<===\n",  lit);
};

void operator "" _dump(const char16_t* lit, size_t)
{
    printf("operator \"\" _dump(const char16_t*, size_t):\n"                  );
};

void operator "" _dump(const char32_t* lit, size_t)
{
    printf("operator \"\" _dump(const char32_t*, size_t):\n"                  );
};

// Raw literal operator
void operator "" _dump_raw(const char* lit)
{
    printf("operator \"\" _dump_raw(const char*)        : ===>%s<===\n",   lit);
};

template<char...> void operator "" _dump_template();       // Literal operator template

int main(int argc, const char* argv[])
{
    42_dump;
    3.1415926_dump;
    3.14e+25_dump;
     'A'_dump;
    L'B'_dump;
    u'C'_dump;
    U'D'_dump;
      "Hello World"_dump;
     L"Wide String"_dump;
    u8"UTF-8 String"_dump;
     u"UTF-16 String"_dump;
     U"UTF-32 String"_dump;
    42_dump_raw;
    3.1415926_dump_raw;
    3.14e+25_dump_raw;

    // There is no raw literal operator or literal operator template support on these types:
    //  'A'_dump_raw;
    // L'B'_dump_raw;
    // u'C'_dump_raw;
    // U'D'_dump_raw;
    //   "Hello World"_dump_raw;
    //  L"Wide String"_dump_raw;
    // u8"UTF-8 String"_dump_raw;
    //  u"UTF-16 String"_dump_raw;
    //  U"UTF-32 String"_dump_raw;
}
operator "" _dump(unsigned long long int) : ===>42<===
operator "" _dump(long double)            : ===>3.141593<===
operator "" _dump(long double)            : ===>31399999999999998506827776.000000<===
operator "" _dump(char)                   : ===>A<===
operator "" _dump(wchar_t)                : ===>66<===
operator "" _dump(char16_t)               : ===>67<===
operator "" _dump(char32_t)               : ===>68<===
operator "" _dump(const     char*, size_t): ===>Hello World<===
operator "" _dump(const  wchar_t*, size_t): ===>Wide String<===
operator "" _dump(const     char*, size_t): ===>UTF-8 String<===
operator "" _dump(const char16_t*, size_t):
operator "" _dump(const char32_t*, size_t):
operator "" _dump_raw(const char*)        : ===>42<===
operator "" _dump_raw(const char*)        : ===>3.1415926<===
operator "" _dump_raw(const char*)        : ===>3.14e+25<===