References, unlike pointers, aren't objects. That's very important when you argue about the legality of programs under the strict terms of the standard - in particular, whether a program violates the ODR or not.
A trick used in some Boost libraries depends on this. Let's see if I remember.
The problem is this: consider a library that requires some complex, predefined object to be available to the programmer. Let's say, Boost.Lambda and its placeholders, _1, _2 etc.
Lambda is a header-only library. There are no source files to place definitions in. This is a problem, because objects have to be defined. But if you place the definitions in header files, you'll get redefinition linker errors.
No problem, you may think, I'll just use an unnamed namespace (you've considered making the objects static, too, but static is just an inferior method of achieving the same; the entire below discussion applies to static stuff, too), and all problems will go away:
Code:
namespace boost { namespace lambda {
namespace {
very_special_type _1;
very_special_type _2;
}
}}
Right?
Not right. Aside from several compilers having trouble with unnamed namespaces in header files due to precompiled headers, there's also a language standard issue. What if the user (or the library, for that matter) has an inline function in a header?
Code:
inline void foo()
{
namespace ll = boost::lambda;
some_algorithm(some_parameters, ll::_1 + ll::_2);
}
This looks legal. Only, it isn't.
With inline functions, the compiler (or linker, but the language definition calls it all the implementation) has to accept that every file contains a definition of the function. It has to collapse these definitions, and use just one. However, that means that all definitions must be exactly the same.
They aren't. Let's review what we know about unnamed namespaces.
Code:
namespace
{
declarations;
}
This is equivalent to
Code:
namespace unique_name {}
using namespace unique_name;
namespace unique_name
{
declarations;
}
where unique_name is a name that is unique to each translation unit. This means that, since code outside the translation unit can't recreate the name, they cannot refer to elements of the namespace (except indirectly, through pointers, references and template parameters).
The net effect of this is that in translation unit 1, the placeholder objects are boost::lambda::unique1::_1 etc., whereas in translation unit 2, the placeholder objects are boost::lambda::unique2::_1 etc. The inline function foo(), present in both translation units, refers to the unique1 variants of the placeholders in translation unit 1, to the unique2 variants in the other. In other words, the definition of foo() differs between the translation units; the ODR has been violated.
Does it matter in the practical world?
Apparently. Boost enforces a rule that unnamed namespaces are disallowed in their headers. There's a real reason for that, not just some language lawyer stuff. We've had problems, though I can't remember what they were.
Sooooo ... where do references come in?
Well, if we can't use unnamed namespaces, we've got to find another way. There's another thing that the compiler collapses between translation units, and that's templates. You can put a template definition in a header file - in fact you have to - and the compiler will accept it, no matter in how many files the definition is. So you could declare the placeholders like this:
Code:
template <typename T>
struct placeholders
{
static const very_special_type _1;
static const very_special_type _2;
};
template <typename T>
const very_special_type placeholders<T>::_1;
template <typename T>
const very_special_type placeholders<T>::_2;
OK, but now the syntax is awkward. Not only does client code have to refer to the placeholders as, for example, placeholders<int>::_1, but you actually have to enforce the same type, because otherwise the objects aren't the objects your library uses, and it all wouldn't work.
The second is easy to deal with.
Code:
typedef placeholders<int> args;
Now it's args::_1 and args::_2. Better. Still, not quite satisfactory.
And now references come in. We go back to the unnamed namespace.
Code:
namespace
{
const very_special_type &_1 = placeholders<int>::_1;
const very_special_type &_2 = placeholders<int>::_2;
}
Now _1 and _2 can be used plainly.
Because references aren't objects, they're just object aliases. And in the definition of an inline function, the actual objects count, which are the same for all translation units, even if the references are different.
Admittedly, it's a grey area of the standard. You can make a case for both interpretations. But that's enough - together with the fact that the real-world problems didn't occur with this solution - to make Boost accept this variant.
Phew, that was long and useless. But it should show that there are differences between pointers and references beside syntax.