Nested namespaces useless typing?

Started by
8 comments, last by Juliean 6 years, 5 months ago

Do you think that C++ api structured by nested namespaces are a good thing?

Or do you see its just useless typing and it should rather have a prefix or something?

Or do you think that one or two levels are just fine?

What do you think?

 

I personally think that C++ namespaces are there to prevent conflicts between other api´s and for nothing else. But for medium to large size api´s one nested level is fine.

Too long: my::IO::Path::Combine(my::IO::Path::GetHome(), my::IO::Path::ExtractFileName(...));

Fine: my::Paths::CombinePath(my::Paths::GetHomePath(), my::Paths::ExtractFileName(...));

Better (using namespace my): Paths::CombinePath(Paths::GetHomePath(), Paths::ExtractFileName(...));

Advertisement
4 hours ago, Finalspace said:

Or do you see its just useless typing and it should rather have a prefix or something?

You mean, is it better to use an in-language namespace mechanic or an extra-lingual one (ie. prefixes)?

Consider Koenig lookup.

Stephen M. Webb
Professional Free Software Developer

6 minutes ago, Bregma said:

You mean, is it better to use an in-language namespace mechanic or an extra-lingual one (ie. prefixes)?

Consider Koenig lookup.

I am talking about nested namespaces in C++. For example:


// One nested namespace

namespace my {
  namespace paths {
    const char *ExtractFileName(const char *path);
  };
  namespace strings {
    size_t GetAnsiStringLength(const char *str);
  };
};

// Multiple nested namespaces

namespace my {
  namespace system {
    namespace io {
      namespace path {
        const char *ExtractFileName(const char *path);
      };
    };
  };
  namespace strings {
    size_t GetAnsiLength(const char *str);
  };
};


// Single namespace with category prefix

namespace my {
  const char *PathExtractFileName(const char *path);
  size_t StringGetAnsiLength(const char *str);
};

 

Using C++? Use namespaces. Using C? Namespaces are unavailable, so don't use namespaces.

However, that doesn't mean you need to have a category for everything you can possibly imagine. You should choose your namespaces based on how many functions/classes/variables/whatever are in those namespaces. In some cases, yes, nesting namespaces is a good idea.

I'm going to reference Python since I am very familiar with it. In the Python standard API, one of the basic modules is the "os" module. A module such as this in Python is roughly the same thing as a namespace in C++. The os module has a sub-module in it, "os.path", because there are a sufficient number of functions in that module to warrant the separation, but closely enough related to the os module to not warrant it being a completely separate module. This is a good example of namespaces done right.

So going back to your example, no, I don't think that level of nesting is necessary. A lot of it is superfluous, and superfluous is inefficient. If you only have two things, keep them under the same namespace. If you actually have 20 things in each, change my::system::io::path to just my::path.

But ultimately, do what's best for you and anyone else working on the program. The key point is to make the code readable, not to adhere to a particular style doctrine.

I guess it depends on your architecture.

You can do it without namespaces, the old-school way, with prefixing everything. But if one day you decide to move to namespaces, then whether you'll keep the names with the prefixes and thus have some kind of redundancy, plus obliging the client to type more characters for no real uses (ie like Qt), or whether you'll have to remove the prefixes but then the clients will loose their habits (where is that pref1prefix2Class ?)

Or you can do it with namespaces. You can do it the C++ standard way, with only a single prefix (or another nested one for their experimental things). Or you can do it with several namespaces. But then you'll certainly want a main namespace to keep things logically ordered (otherwise the client will wonder if two classes of different namespaces belong to the same project/API or not). And then an issue for the client will be to wonder from which namespace a tool belong to...

I tend to agree with most in terms that 'it depends'.  Take your path example, oddly enough that is exactly a case I run into very often.  IO::Path:* can be appropriate given that you might also have AI::Path::* or DAG::Path::*.  Yes, I could rely on prefixing such as "IOPath", "AIPath" or "DAGPath" and avoiding the namespace but to me the purpose of the namespace here is to allow each API to use the most appropriate and obvious names without stomping on each other.    Once out of headers and in the CPP's where you use such things, simply typing in 'using namespace IO/AI/DAG' generally takes care of the extra typing involved.  The rare exceptions where you need combinations, serialization comes to mind, those are the only places you usually need to continue typing in the prefixes.

As to the depth of the namespaces.  I tend to believe 2-3 deep is appropriate with the only exceptions usually being things like an internal namespace that pushes into the 4+ range.  2-3 may seem a bit much but it all comes out of usability needs.  Probably the most common header name in nearly any library you get: '#include "Math.h[pp]"'.  This is a common problem if you are using 3rd party libraries: who's math file are you including with that?  So, in order to make sure I don't have that problem, there is *always* a prefix directory in my libraries: '#include "LIB/Math.hpp"' which guarantee's I get *my* math header file.  Out of this flows the rule I use that including a file should be as close to namespace of the object it provides as possible.  I.e. '#include "LIB/IO/Path.hpp"' gives me LIB::IO::Path ready for use.

While my rules don't work for everyone and are a bit excessive for normal everyday work, there is one rule I suggest anyone follow: "Be Consistent".  If you adopt a set of rules and styles, apply it everywhere with as few exceptions as possible.  Consistency trumps individual decisions folks may dislike pretty much every time.

Namespaces are part of the interface.  Used well they help tremendously.  Used poorly they are nothing more than extra typing.

Designing efficient interfaces, including bundling into namespaces, is a difficult task generally requiring refinement over time.

It is also highly dependent on context.  Namespaces are a powerful organizational feature, and the types of organization depend on the nature of the code.

At first glance your example of my::system::io::path::ExtractFileName() seems an example of the poorly-executed variety, although it if the system happens to be rather complex with many systems and subsystems regarding the system, and many subsystems with in IO, and many operations and systems within path management, then it may be executed well.

 

On 11/17/2017 at 6:48 AM, Finalspace said:

I am talking about nested namespaces in C++

Indeed.

You can namespace your identifiers in C++ using extra-lingual mechanisms like a prefix.  For example, my_strings, my_system_io_paths.  Programmers who are using C paradigms in C++ or who come from languages that provide no built-in mechanic for namespacing like to do that.  It usually works for the, until it causes a problem.

You can also use the in-language namespace mechanic in C++.  For example, my::strings, my::system::io::paths.  The mechanic was not developed in a vacuum but in response to real-life issues encountered frequently enough in practice to justify a language feature.

The primary magic available only if you're leveraging the built-in language mechanic is something called name-dependent lookup, also known as Koenig lookup.  That spell gets invoked when you're trying to resolve overloads, and becomes particularly important when you're using templates.  Namespaces and templates work together in lockstep in interesting and effective ways with the end result that (usually) they do what you expect and they continue to do so when your code base is in the professional production domain of millions of lines of code.  The same can not really be said of using the C-style prefix namespacing and preprocessor macros.

How deep should you namespace things?  Well, the answer is as deep as you need but no deeper.  I would argue they should match the levelling of your project as descibed in Lakos' monograph Large-Scale C++ Software Design, which sort of implies if you're in the millions of lines of code you'll probably have around 4 namespace levels, but of course your project may vary.

But don't worry, you'll probably have the same number of levels even if you're using the C-style prefix method of namespacing.  You'll just be doing it without the benefit of name-dependent lookup or the possibility of a using shortcut.

Stephen M. Webb
Professional Free Software Developer

On 17.11.2017 at 12:48 PM, Finalspace said:

I am talking about nested namespaces in C++. For example:

Note that in C++17, there is a feature actually called "nested namespaces", which can make such code way easier to read/write:


// might be worth a discussion if this is actually better when there are multiple nested namespaces in one file
namespace my::paths {
   const char *ExtractFileName(const char *path);
namespace my::strings {
   size_t GetAnsiStringLength(const char *str);
};

// could instead look like this:
namespace my {
  namespace system::io::path {
     const char *ExtractFileName(const char *path);
  };
  namespace strings {
    size_t GetAnsiLength(const char *str);
  };
};

 

On 17.11.2017 at 8:18 AM, Finalspace said:

I personally think that C++ namespaces are there to prevent conflicts between other api´s and for nothing else. But for medium to large size api´s one nested level is fine.

They are also a better alternative for "smurf naming convention". I worked at a project where every class was prefixed with proj_module_nested_ ... you could argue that this is already an iditiotic naming scheme, but in multi-million line projects, its important to be able to quickly see to which module a certain method/class belongs.

 

On 17.11.2017 at 8:18 AM, Finalspace said:

Too long: my::IO::Path::Combine(my::IO::Path::GetHome(), my::IO::Path::ExtractFileName(...));

Fine: my::Paths::CombinePath(my::Paths::GetHomePath(), my::Paths::ExtractFileName(...));

Better (using namespace my): Paths::CombinePath(Paths::GetHomePath(), Paths::ExtractFileName(...));

I'd actually prefer IO::Paths::GetHome() etc... I tend to group my global methods in a static class instead of a namespace, so I'm just used to seeing multiple levels of nested :: without a problem. I also tend to not using namespace, but instead put all implementations in their respective namespace as well:


namespace my::strings
{
	const char* something(const char* path)
	{
		IO::Paths::GetHomePath(); // my is implicitely available, as well as my::strings. 
    }
}

But thats just my sense, in case you want to get some different ideas.

This topic is closed to new replies.

Advertisement