We are a C++ library. For years we had typedef unsigned char byte;
in the global namespace. User programs and other libraries provided compatible definitions of byte
, so there were no problems.
C++17 added std::byte
and changed semantics of a byte. Now we need to be more hygienic by avoid global namespace pollution; and we need to insulate ourselves from std::byte
. Our change is to move our byte
into our namespace.
We are witnessing an unexpected failure as we test the impact of changes. The program below does not follow best practices (according to Herb Sutter at Migrating to Namespaces), but we expect it to be a typical use case for user programs.
$ cat test2.cxx
#include "cryptlib.h"
#include <iostream>
using namespace std;
using namespace CryptoPP;
// Testing this to select the right byte type
using byte = CryptoPP::byte;
int main(int argc, char* argv[])
{
CryptoPP::byte block1[16];
std::byte block2[16];
byte block3[16];
return 0;
}
The program above has competing definitions of byte
due to std::byte
, CryptoPP::byte
and the using namespace ...
. As I said, I know it leaves something to be desired.
Compiling the program results in (sans the unused warnings):
$ echo $CXXFLAGS
-DNDEBUG -g2 -O3 -std=c++17 -Wall -Wextra
$ g++ $CXXFLAGS test2.cxx ./libcryptopp.a -o test.exe
test2.cxx: In function ‘int main(int, char**)’:
test2.cxx:12:3: error: reference to ‘byte’ is ambiguous
byte block3[16];
^~~~
test2.cxx:6:28: note: candidates are: using byte = CryptoPP::byte
using byte = CryptoPP::byte;
^
In file included from stdcpp.h:48:0,
from cryptlib.h:97,
from test2.cxx:1:
/usr/include/c++/7/cstddef:64:14: note: enum class std::byte
enum class byte : unsigned char {};
^~~~
In the compile error, what is catching me by surprise is, we explicitly removed the ambiguity with using byte = CryptoPP::byte;
.
We hoped to advise users who depend upon byte
in the global namespace (and who use the using namespace ....
declarations) to use using byte = CryptoPP::byte;
until they had time to update their code.
My first question is, why is the compiler claiming byte
is ambiguous after it was told the to use CryptoPP::byte
via the using
declaration? Or is this just plain wrong, and we are lucky we got that far along during the compile?
A second related question is, is there any advice we can give to users so their existing code compiles as expected after we migrate byte
into our namespace? Or is the only choice for users to fix their code?
I think this has something to do with the issue: Context of using declaration and ambiguous declaration. The using byte = CryptoPP::byte;
is tripping me up since the ambiguity was removed.
Regarding comments below and "I think there's a lesson to be learned from this about intelligent use of namesapces from the get-go", there's some backstory. This is Wei Dai's Crypto++. It was written in the early 1990s, and used unscoped byte
because C++ namespaces were not available. Namespaces appeared about 5 years later.
When namespaces were introduced, everything was moved into CryptoPP
except byte
. According to source code comments, byte
remained in global namespace due to "ambiguity with other byte typedefs". Apparently, there was contention long before C++17. Early C++ compilers probably did not help the issue.
In hindsight, we should have planned for some version of C++ doing this (in addition to a bad using namespace ...
interaction). We should have already moved to CryptoPP::byte
, and possibly provided an unscoped byte
for convenience to user programs with appropriate warnings.
Hindsight is always 20/20.
A using-directive in the global namespace causes unqualified name lookup to consider all declarations in the nominated namespace as members of the global namespace. They stand on equal footing with other members of the global namespace, and so adding additional declarations to the global namespace will not resolve an existing ambiguity in unqualified lookup.
Such declarations can resolve qualified name lookup ambiguities (e.g., the lookup of byte
in ::byte
), because that lookup only examines namespaces nominated by using-directives if a declaration is not found. That might be where you got the idea.
I'd be strongly inclined to demand that people explicitly name the namespace of your library. C++ is about knowing types, not guessing them.
#include <iostream>
#include <cstddef>
/* simulate CrypoPP for the MVCE */
namespace CryptoPP {
using byte = unsigned char;
}
/* never, ever, ever. People who do this invite their own destruction.
using namespace std;
using namespace CryptoPP;
using byte = CryptoPP::byte;
*/
namespace OurFunkyLibrary
{
using byte = std::byte; // or CryptoPP::byte, as you wish
}
int main(int argc, char* argv[])
{
// explicit
CryptoPP::byte block1[16];
// explicit
std::byte block2[16];
/* if your users really can't stand knowing which type they are using... */
using namespace OurFunkyLibrary;
byte block3[16];
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With