c++ - template - В чем преимущество std:: literals::.. встроенных пространств имен?



tagged template literals (1)

В стандарте C ++ (например, N4594) есть два определения для operator""s :

namespace std {
...
inline namespace literals {
inline namespace chrono_literals {
// 20.15.5.8, suffixes for duration literals
constexpr chrono::seconds operator"" (unsiged long long);

и string конечно:

namespace std { 
....
inline namespace literals {
inline namespace string_literals {
// 21.3.5, suffix for basic_string literals:
string operator "" s(const char* str, size_t len);

Интересно, что получается из этих пространств имен (и всех других пространств имен внутри std::literals ), если они inline .

Я думал, что они были в отдельных пространствах имен, поэтому они не конфликтуют друг с другом. Но когда они inline , эта мотивация отменяется, верно? Редактировать: потому что Бьярне объясняет, что основной мотивацией является «управление версиями библиотеки», но здесь это не подходит.

Я вижу, что перегрузки для «Seconds» и «String» различны и поэтому не конфликтуют. Но будут ли они конфликтовать, если перегрузки были одинаковыми? Или namespace ( inline ?) Как-то предотвращает это?

Следовательно, что получается, если они вообще находятся во inline namespace ? Как, как указывает @Columbo ниже, разрешена перегрузка между встроенными пространствами имен и конфликтуют ли они?


Определяемый пользователем литерал s не «конфликтует» между seconds и string , даже если они оба находятся в области видимости, потому что они перегружаются, как любая другая пара функций, в своих разных списках аргументов:

string  operator "" s(const char* str, size_t len);
seconds operator "" s(unsigned long long sec);

Это подтверждается запуском этого теста:

void test1()
{
    using namespace std;
    auto str = "text"s;
    auto sec = 1s;
}

При using namespace std оба суффикса находятся в области видимости, но не конфликтуют друг с другом.

Так почему же танец inline namespace ?

Обоснование состоит в том, чтобы позволить программисту выставлять как можно меньше стандартных имен. В тесте, приведенном выше, я «импортировал» всю библиотеку std в test , или, по крайней мере, столько, сколько было включено.

test1() не работал бы, если бы namespace literals не были inline .

Вот более ограниченный способ использовать литералы, не импортируя весь стандарт:

void test2()
{
    using namespace std::literals;
    auto str = "text"s;
    auto sec = 1s;
    string str2;  // error, string not declared.
}

В результате вводятся все литералы, определенные в std, но не (например) std::string .

test2() не будет работать, если namespace string_literals не было inline а namespace chrono_literals не было inline .

Вы также можете просто выставить строковые литералы, а не хронологические литералы:

void test3()
{
    using namespace std::string_literals;
    auto str = "text"s;
    auto sec = 1s;   // error
}

Или просто хронологические литералы, а не строковые литералы:

void test4()
{
    using namespace std::chrono_literals;
    auto str = "text"s;   // error
    auto sec = 1s;
}

Наконец, есть способ раскрыть все имена хроно и chrono_literals:

void test5()
{
    using namespace std::chrono;
    auto str = "text"s;   // error
    auto sec = 1s;
}

test5() требует немного магии:

namespace chrono { // hoist the literals into namespace std::chrono
    using namespace literals::chrono_literals;
}

Таким образом, inline namespace являются инструментом, который делает все эти опции доступными для разработчика.

Обновить

ОП задает несколько хороших последующих вопросов ниже. Они (надеюсь) рассматриваются в этом обновлении.

using namespace std не очень хорошая идея?

Это зависит. using namespace никогда не является хорошей идеей для глобальной области видимости в заголовке, который должен быть частью библиотеки общего назначения. Вы не хотите вводить несколько идентификаторов в глобальное пространство имен вашего пользователя. Это пространство имен принадлежит вашему пользователю.

Глобальная область, using namespace может быть в заголовке в порядке, если этот заголовок существует только для приложения, которое вы пишете, и если вы согласны, у вас есть все эти идентификаторы, доступные для всего, что включает этот заголовок. Но чем больше идентификаторов вы добавляете в свою глобальную область, тем больше вероятность того, что они будут конфликтовать с чем-то. using namespace std; вводит кучу идентификаторов и будет вводить еще больше с каждым новым выпуском стандарта. Поэтому я не рекомендую using namespace std; в глобальном масштабе в заголовке даже для вашего собственного приложения.

Однако я мог видеть using namespace std::literals или using namespace std::chrono_literals в глобальной области видимости в заголовке, но только для заголовка приложения, а не заголовка библиотеки.

Мне нравится использовать директивы в области видимости функции, поскольку импорт идентификаторов ограничивается областью действия функции. При таком ограничении, если конфликт действительно возникает, его гораздо легче исправить. И это менее вероятно произойдет в первую очередь.

Стандартные литералы, вероятно, никогда не будут конфликтовать друг с другом (они не сегодня). Но ты никогда не знаешь ...

Определяемые std литералы никогда не будут конфликтовать с пользовательскими литералами, поскольку определяемые std литералы никогда не будут начинаться с _ , а пользовательские литералы должны начинаться с _ .

Кроме того, для разработчиков библиотек необходимо (или хорошая практика) не иметь конфликтующих перегрузок внутри нескольких встроенных пространств имен большой библиотеки?

Это действительно хороший вопрос, и я полагаю, что жюри еще не решено. Однако мне просто довелось разрабатывать библиотеку, в которой целенаправленно содержатся конфликтующие пользовательские литералы в разных встроенных пространствах имен!

https://github.com/HowardHinnant/date

#include "date.h"
#include "julian.h"
#include <iostream>

int
main()
{
    using namespace date::literals;
    using namespace julian::literals;
    auto ymd = 2017_y/jan/10;
    auto jymd = julian::year_month_day{ymd};
    std::cout << ymd << '\n';
    std::cout << jymd << '\n';
}

Приведенный выше код не может быть скомпилирован с этим сообщением об ошибке:

test.cpp:10:20: error: call to 'operator""_y' is ambiguous
    auto ymd = 2017_y/jan/10;
                   ^
../date/date.h:1637:1: note: candidate function
operator "" _y(unsigned long long y) NOEXCEPT
^
../date/julian.h:1344:1: note: candidate function
operator "" _y(unsigned long long y) NOEXCEPT
^

_y используется для создания year в этой библиотеке. И в этой библиотеке есть и григорианский календарь (в «date.h»), и юлианский календарь (в «julian.h»). Каждый из этих календарей имеет класс года: ( date::year и julian::year ). Это разные типы, потому что григорианский год - это не то же самое, что юлианский год. Но все же удобно называть их оба year и давать буквально оба.

Если я using namespace julian::literals; из кода выше, то он компилирует и выводит:

2017-01-10
2016-12-28

что является демонстрацией того, что 2016-12-28 юлианский день совпадает с григорианским 2017-01-10. И это также наглядная демонстрация того, что один и тот же день может иметь разные годы в разных календарях.

Только время покажет, будет ли мое использование конфликтующих _y s проблематичным. На сегодняшний день этого не было. Однако не многие люди использовали эту библиотеку с негригорианскими календарями.





inline-namespaces