byte and ambiguous symbol due to using declarations?

Viewed 15454

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.

2 Answers
Related