использование foo :: bar не маскирует typedef-name :: bar?

0

Этот фрагмент не компилируется:

// application code
namespace google_breakpad {
  class ExceptionHandler {
    ExceptionHandler(const char *, int);
  };
}
extern void bar(google_breakpad::ExceptionHandler *);

// from an unavoidably included system header
typedef int (*ExceptionHandler)(void *);

// more application code...
using google_breakpad::ExceptionHandler;
void foo(const char *s)
{
  bar(new ExceptionHandler(s, 0));
}

Как видно из названия, это сокращается из реальной программы. Мое предположение заключалось в том, что независимо от того, какие заголовки систем могли быть сброшены в глобальное пространство имен, using google_breakpad::ExceptionHandler будет подавлять любой другой тип с именем ExceptionHandler и использовать голый ExceptionHandler внутри foo однозначно ссылающийся на класс в namespace google_breakpad.

Однако g++ и клан g++ согласны с тем, что это неверно, настаивая на интерпретации голого ExceptionHandler в качестве ссылки на typedef-name в глобальном пространстве имен.

$ clang++ -fsyntax-only -std=c++11 test.cc
test.cc:14:11: error: excess elements in scalar initializer
  bar(new ExceptionHandler(s, 0));
          ^                 ~~~

$ g++ -fsyntax-only -std=c++11 test.cc
test.cc: In function ‘void foo(const char*):
test.cc:14:32: error: new initializer expression list treated as compound expression [-fpermissive]
   bar(new ExceptionHandler(s, 0));
                                ^
test.cc:14:32: error: invalid conversion from ‘int to ‘ExceptionHandler {aka int (*)(void*)} [-fpermissive]
test.cc:14:33: error: cannot convert ‘int (**)(void*) to ‘google_breakpad::ExceptionHandler* for argument ‘1 to ‘void bar(google_breakpad::ExceptionHandler*)
   bar(new ExceptionHandler(s, 0));
                                 ^

(-std=c++11 фактически не влияет на поведение компилятора, но именно так компилируется исходная программа.)

Q1: Правильно ли работают компиляторы?

Q2: Предполагая, что это так, есть ли способ подавить нежелательное имя typedef и сделать эту программу действительной? (Без изменения каких-либо typedef google_breakpad::ExceptionHandler BreakpadEH знаю, что я могу заменить using с typedef google_breakpad::ExceptionHandler BreakpadEH а затем изменить все последующие использования голого имени, но это просто перемещает проблему вокруг --- откуда я знаю, что это имя не загрязнены системными заголовками? (реальная единица перевода завершается, включая порядка 500 системных заголовков, почти все из которых специфичны для ОС и написаны людьми, которые, похоже, вообще не заботятся о загрязнении пространства имен).)

Теги:
namespaces

1 ответ

1
Лучший ответ

В общем случае вам не должно быть позволено объявлять новый тип с тем же именем, что и уже объявленный в той же области, но, возможно, это не так для классов и typedefs, поскольку вы можете технически проинструктировать компилятор о том, как отличить их. Если бы у вас был другой typedef и, например, он попытался использовать ключевое слово using, он бы пожаловался, что он уже объявлен. Использование ключевого слова using по таким типам не отменяет старый тип, а скорее добавляет объявление в текущую область, поэтому компилятору не нужно искать его далеко. Вы все еще не можете повторно объявить один и тот же тип, поэтому вы не можете ожидать, что это сработает:

typedef int A
typedef char A;

или это

namespace X {
  typedef int A;
}
typedef char A;
using X::A;

Повторное использование такого класса кажется некоторой странностью, потому что это не работает:

typedef int A;
class A;

но это прекрасно работает

typedef int A;
namespace X {
  class A;
}
using X::A;

Конечно, там, где возникает ваша ошибка. Вы технически переопределили тип, который действительно не должен произойти, но он, похоже, квази-работу с некоторыми хаками.

В любом случае у вас есть несколько вариантов, ни один из которых, вероятно, не является именно тем, что вы хотите. Вы можете заставить компилятор искать "класс":

bar(new class ExceptionHandler(s, 0));

Или вы можете использовать оператор using в функции, поскольку ExceptionHandler еще не объявлен в этой области:

void foo(const char *s)
{
  using google_breakpad::ExceptionHandler;
  bar(new ExceptionHandler(s, 0));
}

Или вы можете поместить foo в пространство имен и вызвать его:

namespace google_breakpad {
  void foo(const char *s)
  {
    bar(new ExceptionHandler(s, 0));
  }
}
using google_breakpad::foo;

Или вы можете просто использовать идентификатор пространства имен:

bar(new google_breakpad::ExceptionHandler(s, 0));

Но вы не можете повторно объявить один и тот же тип в той же области действия и рассчитывать на то, чтобы уйти с ним чисто. По крайней мере, это мое понимание этого. YMMV. :-)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню