byte and ambiguous symbol due to using declarations?

12,149

Solution 1

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.

Solution 2

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;
}
Share:
12,149
jww
Author by

jww

Updated on June 04, 2022

Comments

  • jww
    jww almost 2 years

    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.