-2

It's been considered for a reason, that using for namespaces/names is side-effect-prone, and generally, fully qualified names should be preferred.

I've come up with an approach on that, which I couldn't find implemented yet, so I created my own project.

Briefly, the idea is to define a set of macro which would unfold into the following structure:

namespace your_namespace {
    namespace __local__ {
            //includes and other non-exported names go here
        namespace __exported__ {
            //actual code goes here
        }
    }
    using namespace __local__::__exported__;
}

It isolates the contents of __local__ from your_namespace, while keeping them visible to it's logically-actual part.

General usage looks like this:

#include <namespace_util.hpp>

NAMESPACE(foo)
//here go using declarations, and the names you don't want to be visible from outside
NAMESPACE_EXPORTS
//here go your namespace members
NAMESPACE_END

The implementation:

#define NAMESPACE(name)\
    namespace name {\
        namespace __local__ {\

#define NAMESPACE_EXPORTS\
            namespace __exported__ {

#define NAMESPACE_END\
            }\
        }\
        using namespace __local__::__exported__;\
    }

Since I'm relatively new to C++, I can't be completely sure about the solution, so I ask the people: please see the (much more detailed) README, the code, and give your feedback.

Thank you.

  • So you are "relatively new to C++" and come up with an approach that you "couldn't find implemented yet." Sounds like this should be on codereview. – Sjoerd Apr 19 '17 at 20:47
  • They kicked me in here :) – oleg.lukyrych Apr 19 '17 at 22:09
  • 2
    "using for namespaces/names is side-effect-prone" What is side-effect-prone supposed to mean? using just allows you to refer to things without the qualified name, it's perfectly usable in individual translation units, and you can minimize issues by using specific functions/classes/etc. rather than the whole namespace (using namespace does basically defeat the purpose of namespaces, if that's what you meant). In fact, if you want to do proper ADL with std::swap you need using std::swap inside your swap implementations. – JAB Apr 20 '17 at 16:19
  • >>What is side-effect-prone supposed to mean?>> this have been answered by me across the comments. I will update the post or make a self-answer with the summary soon. – oleg.lukyrych Apr 20 '17 at 16:59
10

Your approach is not technically wrong (except a minor detail, see footnote) but it goes against established usage patterns and obfuscates code. I.e. it lowers readability and increases the likelihood of errors. That might be justified if it is outweighed by a clear and significant advantage somewhere else. And here lies the problem. Sjoerd already pointed it out in his answer, but let me reiterate: Precicely what is the problem you are trying to solve? Why is your solution significantly better than the established one? An advantage is not apparent at all, so considering the drawbacks this approach is not useful.

The most severe problem I see are the NAMESPACE macros. They replace basic structural C++ elements, essentially creating a language inside the language. The structure of namespaces and symbol names inside is a central part of the API a header exposes. Obfuscating that structure makes it much harder to understand what’s going on in that header.

Looking at the pattern itself, I’m still not convinced. You’ve now made it clearer in some comments and your updated question that you want to be able to use names from the __local__ namespace without qualifying their names and without the transitive effects of using namespace. On the one hand that can indeed be a benefit for readability, especially if it avoids awkward line breaks because of long qualified names. On the other hand a specific using or using namespace inside a function has the same effect where it’s really needed. Also declaring your external symbols in a namespace that is a child of an internal namespace and then pulling them out of there again with a using is not very elegant. All in all I find the pattern not convincing, but mostly harmless (not regarding the macros!).

For comparison: This is the idiomatic pattern to hide private symbols, i.e. a developer familiar with the C++ ecosystem will expect to see it and intuitively know what it means:

// some_file.cpp
namespace my_ns {
    namespace {
        // Most private symbols go here.
        const int foo = 1;
    }

    int double_foo() {
        return foo * 2;
    }
}


// some_file.hpp
namespace my_ns {
    namespace detail {
        // Private names that cannot be hidden in the .cpp
        // go here. Other common names for this namespace are:
        // "impl", "implementation", sth with "private"
        const int bar = 2;
    }

    int double_foo();

    // function that cannot be implemented in the .cpp
    template <typename T>
    void add_bar(const T& to) {
        using namespace detail;
        // or use the qualified name instead
        to += bar;
    }
}

Footnote: Minor technical detail: A lot of underscored names are reserved for the implementation (of the standard). See the standard, section [reserved.names] for full details. In your code the double-underscored names are problematic. The be-on-the-safe-side rule is: Do not create your own names starting with an underscore and avoid double underscores altogether.

|improve this answer|||||
  • 1) There exists the transitive effect of using (for names). In my opinion, it can be unwanted in some situations, but there's no clear way to disapply it. That's the problem I'm trying to solve. So, I proposed a pattern (no macro at this point) for that purpose. The pattern itself, in a way, relies on semantic encapsulation (underscored prefix/suffix), a common practice to discourage the unintended usage. In addition, I made a couple of macros to simplify the pattern usage. – oleg.lukyrych Apr 20 '17 at 12:34
  • I'm perfectly aware of flaws of macro and underscore encapsulation, yet I think it's the reasonable price for ability to safely use using at namespace level + hide the names within a namespace. – oleg.lukyrych Apr 20 '17 at 12:34
  • 2) In the following excerpt foo actually will have been put into my_ns. Anyone can access it like my_ns::foo. That's a strange meaning of "private". ``` namespace my_ns { namespace { // Most private symbols go here. const int foo = 1; } int double_foo() { return foo * 2; } } ``` – oleg.lukyrych Apr 20 '17 at 12:34
  • 3) You're right on reserved underscored names. I intentionally used them for the sake of demonstration. They should be changed. – oleg.lukyrych Apr 20 '17 at 12:34
  • 2
    Using the macro distracted attention away from the underlying technique, for the reasons I mentioned in my answer. In general, if you want to ask about some code style, hiding it behind a macro is a bad approach, because then people have to pre-process your code in their heads before they can think about it. – Useless Apr 20 '17 at 12:48
8

Looks like a wrong approach, for several reasons:

  1. The things that you don't want to be "visible" to outside, should not go into headers. Instead they go into anonymous namespace in the source file.
  2. Your __local__ things are still visible, and everyone can access them
|improve this answer|||||
  • 1. Debatable. What If I design a header-only library, and for the sake of modularity and brevity want to introduce a namespace tree, and import names to use short id's, without being afraid of side-effects? – oleg.lukyrych Apr 19 '17 at 16:55
  • 2. That's addressed in README. It's discouraged to use internal namespaces directly. I can't fully restrict it, but common sense should imply not to use something named _______do_not_touch_me______ – oleg.lukyrych Apr 19 '17 at 16:57
  • @oleg.lukyrych Header only (I guess something like boost) can be done like you explained: things that should not be used is put into such ("implementation") namespace. For normal code, I would avoid it. Regarding the 2nd comment, don't expect people to read README files or comments - they want to use your library without that. – BЈовић Apr 19 '17 at 16:58
  • 3
    @oleg.lukyrych You are "relatively new to C++." My advice: Accept advice when given, and stop arguing everything. – Sjoerd Apr 19 '17 at 20:49
  • 1
    The "relatively new to C++" is a direct quote from your post. And you're arguing again - proving my point. – Sjoerd Apr 19 '17 at 21:06
6

What problem do you want to solve? That is completely unclear.

As for your code, if you were a junior assigned to me, I would say:

  • Your approach doesn't hide implementation details at all - they are still visible in the header.

  • Your approach hides things behind C-style macros, which are considered "avoid whenever possible" by the C++ community. So replacing a solution not using macros with a solution using macros, is going in the wrong direction.

  • You state

    using for namespaces/names is side-effect-prone

    but write using namespace yourself. So you're inconsistent.

After pointing out those basic mistakes, I wouldn't spend any more time on your code. Instead, I would go back to the main question: What problem do you want to solve?

|improve this answer|||||
  • 4
    @oleg.lukyrych Four comments by you later, and there is still no clear answer to the question "What problem do you want to solve?" If the problem is the supposed "side effect problem" (the transitive effect of using): that's by design, and is not considered a problem at all by the C++ community. – Sjoerd Apr 19 '17 at 21:52
  • 3
    Well, apparently the C++ community uses all those "endless snakes of fully qualified names". You're the only one who thinks it's a problem. – Sjoerd Apr 19 '17 at 22:02
  • 3
    If you want shorter names, use using sh_nm = long::inner::very_long_name; Or typedef the long name. – Sjoerd Apr 19 '17 at 22:06
  • 4
    It's not a problem. Get over it. That's my last advice here. You clearly are not listening to advice - you just want to be confirmed in your believes. – Sjoerd Apr 19 '17 at 22:07
  • 1
    Besides the macro, it's still adding complexity. Since you've consistently refused to provide an example of any code which warrants this complexity, most people are going to say there's no benefit, or insufficient benefit to justify that complexity. Once again, if you do have some minimal code that demonstrates the problem, post that. You're asking for a judgement call (does A justify B) and not showing A. You could ask "could any A justify B", but that's not an interesting question. You actually asked "do you think there will often arise As which justify B", which is opinion. – Useless Apr 20 '17 at 12:52
4

If you really want constructive feedback on this approach, it would have been better to show some actual code that you think benefits from it. Then we'd have something concrete to discuss rather than hypotheticals.

Since there's something like a usage example in your linked README, that should have been in the question: it's helpful to see, and putting essential parts of the question in external links works badly. I didn't even notice the link was there until after I'd read both the question and all the existing answers, and link rot is also a concern.

Now, looking at your sample usage:

#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP

#include <namespace_util.hpp>

INLINE_NAMESPACE(myproject, MYPROJECT_MYCLASS_HPP)

using namespace std;

NAMESPACE_EXPORTS

class MyClass {

};

NAMESPACE_END

#endif

My first thoughts, approaching this without warning, are:

  1. What on earth is this?
  2. Oh good, someone else has written a new sub-language in preprocessor. At least it isn't a horribly broken FOREACH macro this time.
  3. I bet they've written a FOREACH somewhere as well, I'll have to keep an eye out for that.
  4. grep -r '#define private public' ... no matches, what a relief
  5. It's impossible to tell at a glance how to use your class - unless I'm already familiar with your macro idiom, it makes me work harder to figure out it should be called myproject::MyClass
  6. this doesn't seem to save much time or effort over just writing it out directly, with explanatory comments.

    That is, the macro-free version below isn't much more verbose, is no less clear (they're both confusing the first time you see them, and once you're used to the idiom anyway, the macros are no clearer)

Macro-free version:

#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP

namespace myproject {
namespace myclass_detail { // external dependencies go here
  using namespace std;     // scope limited to myclass_extern
  namespace publ {         // public declarations in here
    class MyClass {};
  }
}
using namespace myclass_detail::publ;
}

#endif
|improve this answer|||||
  • Thank you for an attempt to understand. Unfortunately, my thoughts have dispersed over the comments to the answers, but generally, yeah, macro-free version can be used, why not? I made macros as a supplement to the idea. – oleg.lukyrych Apr 20 '17 at 12:50
  • 4
    Never first express your idea using macros - people get hung up on the macro part, and they obscure the code rather than helping. There are lots of reasons to avoid them, and the few exceptions require better justification than "I thought some names looked long and ugly, but I won't show you those names or any code" – Useless Apr 20 '17 at 12:56
1

The purpose of namespaces in C++ is to avoid name clashes between code from different (unrelated) libraries. E.g. libA can define a class or function called libA::foo without clashing with libB::foo. What's more, version 2 of libA can introduce a name that is already used in libB. The code in libB, as well as other code that uses both libA and libB, will continue to compile and behave the same when we upgrade libA from version 1 to version 2.

When you introduce using namespace ... in your code, you lose the protection offered by (proper use of) namespaces. When you upgrade a library whose namespace you imported, your code may fail to compile, due to name clashes. Or (worse), your code may still compile, but behave differently, e.g. because a different function overload is selected during overload resolution.

This problem is not solved by your proposed namespace scheme. When libB depends on libA and imports its namespace, upgrading libA may cause libB to fail to compile (or behave incorrectly).

So, your proposed scheme for the use of namespaces serves a purpose only where the release of dependent libraries are managed collectively. I.e. libB is checked to compile and behave the same whenever libA is modified and/or libB is updated to avoid errors whenever libA is modified. However, in that case it is questionable why these libraries use different namespaces in the first place.

|improve this answer|||||
  • 1) Quote from you: "The purpose of namespaces in C++ is to avoid name clashes". And that's it. The library stuff you mentioned is the application, which is not the only one. – oleg.lukyrych Apr 21 '17 at 10:56
  • 2) I'm not talking about specifically using namespace <name>. As for your example, using <name> should be just fine. – oleg.lukyrych Apr 21 '17 at 10:56
1

I'd like to share my own thoughts to this problem, as I've been toying with this same idea myself.

Your solution using namespaces works in headers, but it obfuscates compiler error messages. An alternative solution would be this:

// header

namespace Internal {
    namespace ProjectNamespace {

        // using declarations, helpers, etc.
        class A { ... };
    }
}

namespace ProjectNamespace {

    using A = Internal::ProjectNamespace::A;
}

With this, two things happen: only one unexpected namespace is shown to the user, Internal, and isn't super weird looking like __local__ or __exports__. Secondly, the namespaces after Internal actually reflect the structure of the API, so the user won't be confused by seeing __export__ as the enclosing namespace, where the user typically gets the most context around whatever they're using.

It may use a using declaration, but typically you only design one function/class per file, so it will be the same number of lines (1) in many situations. It's worth it IMO to get rid of the second unexpected namespace name.

|improve this answer|||||

Not the answer you're looking for? Browse other questions tagged or ask your own question.