Where to put using directives in C++ header files
Asked Answered
A

3

14

For my project I am using some pretty convoluted data structures, e.g.

std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>

for which I would like to declare type aliases for readability. The code on which I built my project on already did this by putting using statements globally in the header files:

// bar.h
#ifndef BAR_H
#define BAR_H

#include <unordered_map>
#include <list>
#include <memory>
#include "foo.h"

using FooTable = std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>;

class Bar {
    FooTable create_foo();
};

#endif

Since my C++ knowledge was a little rusty, I just adopted this style -- but now I read that using using in that way can be problematic, since it forces this alias on everything that includes this header.

Despite a good amount of googling, I could not find a concrete answer on how to handle this properly, only lots of statements on what not to do. So, I just put the using inside the class:

// bar.h
#ifndef BAR_H
#define BAR_H

#include <unordered_map>
#include <list>
#include <memory>
#include "foo.h"


class Bar {
    using FooTable = std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>;

    FooTable create_foo();
};

#endif

However this has the drawback, that I need to restate the alias in the source file:

// bar.cpp
#include "bar.h"

using FooTable = std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>;

FooTable Bar::create_foo()
{
...
}

While this seems to work, I am not sure whether this is safe... and my gut tells me it is kind of ugly. So before I rewrite my whole project like this I thought I'd ask: Is a there a better/more elegant/safer way to do this? Or should I just refrain from using type aliases in header files completely?

Anaximander answered 2/9, 2019 at 12:23 Comment(9)
I never do this at global scope; only within a class or namespace. Seems less polluting that way. Also, as I'm old-fashioned, I tend to use typedef.Lao
You don't need to redefine the type in the source file, just have to remember that the scope of FooTable is in the Bar class. So you need to specify that, as in Bar::FooTable Bar::create_foo()Tuberculosis
@Someprogrammerdude You should make an answer out of that.Lollipop
If you have a namespace, you can pretty much pick. Personally, I go for which one makes sense (as in I put it in a class if the typedef is specifically used for that class, and in a namespace if it's used elsewhere without a connection to a specific class). Also, you don't need to "re-state it", you just need to call it with Bar::FooTable instead.Sum
And you should also define your function as: auto Bar::create_foo() -> FooTable or something like this :-)Arbuckle
You won't find sensible general-purpose answers online, because the correct answer is essentially "it depends". It depends on where you want to use the aliased name. In your case, I would place the using FooTable = ... within the class Bar. When defining Bar::create_foo() in a separate source file, I would specify its return value as Bar::FooTable. But I'm an advocate in ensuring names have the most restrictive scope possible - and you've provided no justification to suggest that FooTable makes sense outside Bar. Other folks may advocate something different.Melodious
Putting it inside a class declaration (private section) inside a header file is fine. No need to restate the alias.Barraza
*write answer* *read comments* *see that all the answer is in the comments already* *shake head vigourously*Helmick
@LightnessRacesinOrbit Well, someone has to put it in a well-formated answer. In this case you did the job, thanks for that! :DEmpery
H
11

However this has the drawback, that I need to restate the alias in the source file:

That is incorrect. You need only make it public then specify the proper scope, so you'd call it Bar::FooTable outside of the scope of Bar (which includes return types, unless trailing!):

Bar::FooTable Bar::create_foo()
{ /* ... */ }

or

auto Bar::create_foo() -> FooTable
{ /* ... */ }

(Just FooTable is fine within the definition, as it's a member!)

Your approach is fine, though I'd put everything in a namespace too. Then it doesn't really matter whether your alias is in the class or not: it's still self-contained within your own code. It becomes purely a matter of style, with little to no impact on anyone else.

Helmick answered 2/9, 2019 at 12:47 Comment(0)
G
4

but now I read that using using in that way can be problematic, since it forces this alias on everything that includes this header.

This problem applies equally much to the class Bar that you define. All programs that include this header are bound to use this definition of Bar.

So, I just put the using inside the class

You've reduced the number of declarations in the global namespace. This is good.

However this has the drawback, that I need to restate the alias in the source file

This is unnecessary.

If you make the alias public, you can refer to it using the scope resolution operator as Bar::FooTable. Or you can use a trailing return type, in which context names are looked up within the scope of the class:

auto Bar::create_foo() -> FooTable

There is a more general solution: namespaces. Put all your own declarations into a single namespace (which can further be divided into subnamespaces). This way you only introduce one name into the global namespace which greatly reduces chance of name conflicts.

Where to put using directives in C++ header files

Same reasoning applies as where you would put any of your other declarations. At least into your own namespace, but generally putting declarations into as narrow scope as is sufficient is a decent rule of thumb. If the type alias is only used with that one class, then a member type alias makes a lot of sense.

Geisler answered 2/9, 2019 at 12:49 Comment(0)
S
0

As already mentioned in the comments, you don't need to re-declare it. You just need to refer to it by using Bar::FooTable if you declare it in a class. It's the same thing if you declare it in a namespace, except you'd use the namespace name if you're outside the namespace. It's also the same thing if you use a typedef.

Whether you declare it in a namespace or in a class is entirely up to you. Personally, I try to make sure it has a scope as relevant as possible. For example, if I have a typedef only used in connection with a specific class, I place the typedef inside the class. If it has global value unrelated to a specific class, I declare it in a namespace.

That being said, I suggest you don't declare it in the global namespace to avoid ambiguity if you for some reason find yourself with a naming conflict if you end up with a different typedef (or I think something else in general with the same name as your typedef/using statement) declared somewhere else.

Also, a typedef in a class is subject to access modifiers. By default, it's private, which means you can't use it outside the class. If you intended to do that, you'll need to make it public.

In terms of safety, declaring it in the global scope is not specifically safe, especially if you combine it with using namespace (which can be a problem of its own - see this). You can declare it in your own namespace though (namespace Baz { using FooTable = blah; /* more code*/}), but declaring it a class creates the same effect.

Note that namespaces and classes are essentially scopes, and they have their own dynamics. If you write code in the source file inside a namespace Baz, you can access a typedef declared in the same namespace without specifying, in this case, Baz::FooTable. It essentially exposes the typedef similarly to how it works in a global namespace, but in a more restricted way. More on that here.

Sum answered 2/9, 2019 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.