Какая польза от std:: litals::.. inline namespaces?

В стандарте С++ (например, 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, эта мотивация отменена, правильно? Edit: Потому что объясняет Bjarne, главной мотивацией является "управление версиями библиотеки", но это не подходит здесь.

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

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

Ответы

Ответ 1

Пользовательский литерал 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. В вышеприведенном тесте я "импортировал" всю библиотеку std в test или, по крайней мере, столько, сколько было #include.

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

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

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 не очень хорошая идея?

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

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

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

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

std-определенные литералы, вероятно, никогда не конфликтуют друг с другом (они не сегодня). Но вы никогда не знаете...

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" ). Каждый из этих календарей имеет класс year: (date::year и julian::year). Они разные, потому что григорианский год - это не то же самое, что юлианский год. Но по-прежнему удобно называть их как year, так и давать им как литерал _y.

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

2017-01-10
2016-12-28

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

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