байт и неоднозначный символ из-за использования объявлений?

Мы являемся библиотекой C++. В течение многих лет у нас было typedef unsigned char byte; в глобальном пространстве имен. Пользовательские программы и другие библиотеки предоставляли совместимые определения byte, поэтому проблем не возникало.

C++17 добавил std::byte и изменил семантику байта. Теперь нам нужно быть более гигиеничными, избегая загрязнения глобального пространства имен; и нам нужно изолировать себя от std::byte. Наше изменение заключается в перемещении нашего byte в наше пространство имен.

Мы наблюдаем неожиданный сбой, когда проверяем влияние изменений. Приведенная ниже программа не соответствует рекомендациям (согласно Хербу Саттеру на Миграция в пространства имен), но мы ожидаем, что это будет типичный вариант использования для пользовательских программ.

$ 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;
}

Программа выше имеет конкурирующие определения byte из-за std::byte, CryptoPP::byte и using namespace .... Как я уже сказал, я знаю, что это оставляет желать лучшего.

Результат компиляции программы (без неиспользуемых предупреждений):

$ 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 {};
              ^~~~

Что меня удивляет в ошибке компиляции, так это то, что мы явно удалили двусмысленность с помощью using byte = CryptoPP::byte;.

Мы надеялись посоветовать пользователям, которые зависят от byte в глобальном пространстве имен (и которые используют объявления using namespace ....), использовать using byte = CryptoPP::byte; до тех пор, пока у них не будет времени обновить свой код.

Мой первый вопрос: почему компилятор утверждает, что byte неоднозначен после того, как ему сказали использовать CryptoPP::byte через объявление using? Или это просто неправильно, и нам повезло, что мы продвинулись так далеко во время компиляции?

Второй связанный с этим вопрос: можем ли мы дать какой-либо совет пользователям, чтобы их существующий код компилировался должным образом после того, как мы перенесем byte в наше пространство имен? Или это единственный выбор для пользователей исправить свой код?

Я думаю, что это как-то связано с проблемой: Контекст использования объявления и неоднозначного объявления. using byte = CryptoPP::byte; сбивает меня с толку, так как двусмысленность была устранена.


Что касается комментариев ниже и «Я думаю, что из этого следует извлечь урок о разумном использовании пространств имен с самого начала», есть некоторая предыстория. Это Crypto++ Вэй Дая. Он был написан в начале 1990-х и использовал byte без области видимости, потому что пространства имен C++ были недоступны. Пространства имен появились примерно через 5 лет.

Когда были введены пространства имен, все было перемещено в CryptoPP, кроме byte. Согласно комментариям к исходному коду, byte осталось в глобальном пространстве имен из-за на "неоднозначность с другими определениями типов байтов". По-видимому, разногласия существовали задолго до C++17. Ранние компиляторы C++, вероятно, не помогли решить проблему.

Оглядываясь назад, мы должны были запланировать, чтобы какая-то версия C++ делала это (в дополнение к плохому using namespace ... взаимодействию). Мы уже должны были перейти на CryptoPP::byte и возможно предоставить неограниченный byte для удобства пользовательских программ с соответствующими предупреждениями.

Задним числом всегда 20/20.


person jww    schedule 16.07.2017    source источник
comment
Лучшим решением, ИМО, было бы не иметь using namespace std; нигде.   -  person Some programmer dude    schedule 16.07.2017
comment
@SomeProgrammerDude - Да, согласен. Мы немного отреагировали на то, что предложили нечто подобное. Некоторые люди предостерегли от этого, потому что это фактически означает, что библиотека устанавливает политику для программы. Я также считаю, что мы не должны принимать политические решения за других. См. также PR 438, используйте ::byte вместо byte.   -  person jww    schedule 16.07.2017
comment
мы явно устранили двусмысленность с использованием byte = CryptoPP::byte; ... Объявления using можно использовать для создания псевдонимов. Их целью не является устранение двусмысленности.   -  person skypjack    schedule 16.07.2017
comment
using byte = CryptoPP::byte; - это просто объявление. Он объявляет псевдоним для CryptoPP::byte в глобальном пространстве имен, но никоим образом не повышает эту версию byte по сравнению с любой другой версией, присутствующей в глобальном пространстве имен.   -  person AnT    schedule 16.07.2017
comment
@AnT — полное ключевое слово [dcl.typedef/1] тогда сильно сбивает с толку.   -  person StoryTeller - Unslander Monica    schedule 16.07.2017
comment
@StoryTeller: Хороший вопрос. Интересно, какова предполагаемая цель этой ключевой фразы.   -  person AnT    schedule 16.07.2017
comment
@AnT - Ничего более конкретного в стандарте я не смог найти. Но с точки зрения функции псевдоним кажется особенным. coliru.stacked-crooked.com/a/7dc4fd61100b5177   -  person StoryTeller - Unslander Monica    schedule 16.07.2017


Ответы (2)


Директива использования в глобальном пространстве имен приводит к неквалифицированному поиск имени, чтобы рассматривать все объявления в указанном пространстве имен как элементы глобального пространства имен. Они равноправны с другими членами глобального пространства имен, поэтому добавление дополнительных объявлений в глобальное пространство имен не устранит существующую неоднозначность в неквалифицированном поиске.

Такие объявления могут разрешать неоднозначности поиска уточненных имен (например, поиск byte в ::byte), потому что этот поиск проверяет только пространства имен, назначенные using-directive, если объявление не найдено. Это может быть то, где вы получили идею.

person T.C.    schedule 16.07.2017
comment
Однако я не вижу, чтобы OP убеждал клиентов исправить свой код и использовать ::byte вместо byte. Не больше, чем они принуждают клиентов использовать CryptoPP::byte. Я думаю, что из этого следует извлечь урок о разумном использовании имен с самого начала. - person StoryTeller - Unslander Monica; 16.07.2017
comment
@StoryTeller Верно, если их клиент может использовать ::byte, он может просто использовать правильно квалифицированную версию. Я просто включил его, чтобы устранить вероятный источник заблуждения ОП. - person T.C.; 16.07.2017
comment
@Т.С. и СтройТеллер - Большое спасибо. Извините, что заставил вас, ребята, играть в языкового юриста. - person jww; 16.07.2017

Я был бы сильно склонен требовать, чтобы люди явно называли пространство имен вашей библиотеки. С++ — это знание типов, а не угадывание их.

#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;
}
person Richard Hodges    schedule 16.07.2017
comment
никогда, никогда, никогда не делайте этого - Лол... Это именно тот случай, который мы пытаемся обойти. Я начинаю думать, что у этой проблемы нет решения из-за неоднозначности использования двух разных пространств имен с одним и тем же символом, но разной семантикой. Тем, кто хочет это сделать, придется смириться с этим и исправить свой код, поскольку С++, похоже, не позволяет нам выбрать ни std::byte, ни CryptoPP::byte. - person jww; 16.07.2017
comment
@jww, если ваши пользователи призывают к собственной гибели, вы не должны стоять у них на пути. - person Richard Hodges; 16.07.2017