What is the benefit of std::literals::.. being inline namespaces?

3.4k Views Asked by At

In the C++-Standard (eg. N4594) there are two definitions for operator""s:

One for std::chrono::seconds :

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

and one for std::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);

I wonder what is gained from those namespaces (and all the other namespaces inside std::literals), if they are inline.

I thought they were inside separate namespaces so they do not conflict with each other. But when they are inline, this motivation is undone, right? Edit: Because Bjarne explains the main motivation is "library versioning", but this does not fit here.

I can see that the overloads for "Seconds" and "String" are distinct and therefor do not conflict. But would they conflict if the overloads were the same? Or does take the (inline?) namespace prevents that somehow?

Therefore, what is gained from them being in an inline namespace at all? How, as @Columbo points out below, are overloading across inline namespaces resolved, and do they clash?

1

There are 1 best solutions below

7
On BEST ANSWER

The user-defined literal s does not "clash" between seconds and string, even if they are both in scope, because they overload like any other pair of functions, on their different argument lists:

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

This is evidenced by running this test:

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

With using namespace std, both suffixes are in scope, and yet do not conflict with each other.

So why the inline namespace dance?

The rationale is to allow the programmer to expose as few std-defined names as desired. In the test above, I've "imported" the entire std library into test, or at least as much as has been #included.

test1() would not have worked had namespace literals not been inline.

Here is a more restricted way to use the literals, without importing the entire std:

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

This brings in all std-defined literals, but not (for example) std::string.

test2() would not work if namespace string_literals was not inline and namespace chrono_literals was not inline.

You can also choose to just expose the string literals, and not the chrono literals:

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

Or just the chrono literals and not the string literals:

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

Finally there is a way to expose all of the chrono names and the chrono_literals:

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

test5() requires this bit of magic:

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

In summary, the inline namespaces are a tool to make all of these options available to the developer.

Update

The OP asks some good followup questions below. They are (hopefully) addressed in this update.

Is using namespace std not a good idea?

It depends. A using namespace is never a good idea at global scope in a header that is meant to be part of a general purpose library. You don't want to force a bunch of identifiers into your user's global namespace. That namespace belongs to your user.

A global scope using namespace can be ok in a header if the header only exists for the application you are writing, and if it is ok with you that you have all of those identifiers available for everything that includes that header. But the more identifiers you dump into your global scope, the more likely it is that they will conflict with something. using namespace std; brings in a bunch of identifiers, and will bring in even more with each new release of the standard. So I don't recommend using namespace std; at global scope in a header even for your own application.

However I could see using namespace std::literals or using namespace std::chrono_literals at global scope in a header, but only for an application header, not a library header.

I like to use using directives at function scope as then the import of identifiers is limited to the scope of the function. With such a limit, if a conflict does arise, it is much easier to fix. And it is less likely to happen in the first place.

std-defined literals will probably never conflict with one another (they do not today). But you never know...

std-defined literals will never conflict with user-defined literals because std-defined literals will never start with _, and user-defined literals have to start with _.

Also, for library developers, is it necessary (or good practice) to have no conflicting overloads inside several inline namespaces of a large library?

This is a really good question, and I posit that the jury is still out on this one. However I just happen to be developing a library that purposefully has conflicting user-defined literals in different inline namespaces!

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';
}

The above code fails to compile with this error message:

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
^

The _y literal is used to create year in this library. And this library has both a Gregorian calendar (in "date.h") and a Julian calendar (in "julian.h"). Each of these calendars has a year class: (date::year and julian::year). They are different types because the Gregorian year is not the same thing as a Julian year. But it is still convenient to name them both year and to give them both a _y literal.

If I remove the using namespace julian::literals; from the code above then it compiles and outputs:

2017-01-10
2016-12-28

which is a demonstration that 2016-12-28 Julian is the same day as 2017-01-10 Gregorian. And this is also a graphic demonstration that the same day can have different years in different calendars.

Only time will tell if my use of conflicting _ys will be problematic. To date it hasn't been. However not many people have used this library with non-Gregorian calendars.