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

1

27

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?

Lichenin answered 21/8, 2016 at 14:21 Comment(3)
I see, your question is about overloading across inline namespaces. Not a dup then.Markusmarl
@Markusmarl Indeed. And it is not a dup for "What are inline namespaces for" either, because that answer is about versioning grm.Lichenin
Re: unsiged long long, perhaps that's a typo for unsigned ....Simonton
N
41

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.

Neoterism answered 21/8, 2016 at 14:47 Comment(7)
So, I do do derive from this, that using namespace std; is not a good idea. Using more specific namespaces like using namespace std::chrono_literals; is? Or is that to radical a conclusion? I understand that the overloads in this case do not conflict, but how about in the future? Also, for library developers, is it necessary (or good practice) to have no conflicting overloads inside several inline namespaces of a large library? It's difficult to derive personal guidelines for this language feature...Lichenin
@towi: Updated answer.Neoterism
Thanks for full-blown update on using namespace std, I should have been more specific -- of course I meant inside of functions or implementation files. But thanks anyway (other readers will like it!). And your Hinnant-Date-Example is excellent and shows me that it is no compiler accident that those overloads do not compile but by-design of the standard. Good. And a final remark to your date-library example: It is the most crazy implementation of type safety with dates I have seen until now... :-)Lichenin
I read through the first few paragraphs and was like "this is probably Howard" :)Dribble
Long winded blow-hard, yeah, I can see it. :-)Neoterism
I could imagine at some point, that there are different headers which define user defined operators with the same parameters. They could not live inside of the same DLL/.so because their binary name conflicts. But if they are inside inline namespaces, they work fine, as long as the programmer doesn't include and tries to use both headers at once. Could that be a reason for inline namespaces aswell?Degreeday
@JohannesSchaub-litb: What you describe is basically what I demonstrate in the date lib example above. One clarification though. This is the behavior of all namespaces. The only magic that inline adds is it implicitly puts a using declaration at the end to import all the names into the outer namespace. I.e. this could all be done just with non-inline namespaces and using directives.Neoterism

© 2022 - 2024 — McMap. All rights reserved.