Странное использование `?:` в коде `typeid`

В одном из проектов, над которым я работаю, я вижу этот код

struct Base {
  virtual ~Base() { }
};

struct ClassX {
  bool isHoldingDerivedObj() const {
    return typeid(1 ? *m_basePtr : *m_basePtr) == typeid(Derived);
  }
  Base *m_basePtr;
};

Я никогда не видел, чтобы typeid так использовали. Почему он танцует этот странный танец с ?:, а не просто с typeid(*m_basePtr)? Может ли быть какая-то причина? Base — полиморфный класс (с виртуальным деструктором).

РЕДАКТИРОВАТЬ: В другом месте этого кода я вижу это, и это кажется эквивалентно "лишним"

template<typename T> T &nonnull(T &t) { return t; }

struct ClassY {
  bool isHoldingDerivedObj() const {
    return typeid(nonnull(*m_basePtr)) == typeid(Derived);
  }
  Base *m_basePtr;
};

person Johannes Schaub - litb    schedule 22.07.2011    source источник
comment
Вы пробовали без этого?   -  person Luke    schedule 23.07.2011
comment
Может быть, это наследие случайно? (Может быть, это не всегда было 1 ? ...)   -  person    schedule 23.07.2011
comment
Дело в том, что условное выражение всегда будет оцениваться как истинное, а две ветви дают одно и то же значение. Можете ли вы посмотреть историю контроля версий (если есть) и посмотреть, было ли это чем-то еще в прошлом?   -  person In silico    schedule 23.07.2011
comment
Я согласен с @pst: скорее всего, наследие.   -  person NotMe    schedule 23.07.2011
comment
Это похоже либо на очень умный способ победить какую-то чрезмерно усердную оптимизацию компилятора, либо на программирование карго-культа.   -  person biziclop    schedule 23.07.2011
comment
@biziclop: Умный - это не то слово, которое я бы использовал. :-П   -  person In silico    schedule 23.07.2011
comment
Человека, написавшего рассматриваемый фрагмент кода, больше нет? Если с ними все еще можно связаться, вы можете попробовать обратиться к ним напрямую и спросить, хотя бы для того, чтобы понять, почему они сделали это в первую очередь.   -  person JAB    schedule 23.07.2011
comment
@In silico Помните, умный != мудрый. :)   -  person biziclop    schedule 23.07.2011
comment
Я, наверное, написал бы это typeid(*m_basePtr ? *m_basePtr : *m_basePtr).   -  person Nemo    schedule 23.07.2011
comment
У меня нет ответа на этот вопрос, но я подозреваю, что код не возвращает то, что мог ожидать автор, если m_basePtr указывает на объект, производный от Derived (если только они действительно не хотели возвращать true только в том случае, если объект был именно типа Derived). И это даже без учета того, указывает ли m_basePtr на тип другого типа, производный от Base, но не входящий в иерархию Derived. Но я мог предположить, что даже если это задумано, это, вероятно, проблематичный дизайн.   -  person Michael Burr    schedule 23.07.2011
comment
@Nemo, но тогда Base нужно будет преобразовать в bool. Неправда в реальном коде проекта.   -  person Johannes Schaub - litb    schedule 24.07.2011


Ответы (4)


Я думаю это оптимизация! Малоизвестная и редко (можно сказать «никогда») используемая особенность typeid заключается в том, что нулевое разыменование аргумента typeid вызывает исключение вместо обычного UB.

Какой? Ты серьезно? Ты пьян?

Конечно. да. Нет.

int *p = 0;
*p; // UB
typeid (*p); // throws

Да, это уродливо, даже по стандарту уродства языка С++.

OTOH, это не работает нигде внутри аргумента typeid, поэтому добавление любого беспорядка отменит эту «функцию»:

int *p = 0;
typeid(1 ? *p : *p); // UB
typeid(identity(*p)); // UB

Для протокола: в этом сообщении я не утверждаю, что автоматическая проверка компилятором того, что указатель не равен нулю, перед разыменованием обязательно является безумием. Я только говорю, что делать эту проверку, когда разыменование является непосредственным аргументом typeid, а не где-либо еще, совершенно безумно. (Возможно, это была шутка, вставленная в какой-то черновик, и так и не удаленная.)

Для справки: я не утверждаю в предыдущем «Для справки», что для компилятора имеет смысл вставлять автоматические проверки того, что указатель не является нулевым, и генерировать исключение (как в Java) при разыменовании нулевого значения. : вообще генерировать исключение при нулевом разыменовании абсурдно. Это ошибка программирования, поэтому исключение не поможет. Требуется отказ утверждения.

person curiousguy    schedule 26.09.2011
comment
Какой? Ты серьезно? Ты пьян? - person Mateen Ulhaq; 27.09.2011
comment
Хорошее объяснение. Я просто надеюсь, что буду помнить об этом всякий раз, когда сталкиваюсь с этой «идиомой»; скорее всего, я не буду (я редко вижу или использую typeid). - person Michael Burr; 19.12.2011
comment
Стандарт требует создания исключения, когда аргументом выражения typeid является lvalue. Произвольное использование тернарного оператора в этом случае не меняет этого поведения, и соответствующий компилятор должен выдать исключение. - person David Rodríguez - dribeas; 14.08.2012
comment
@DavidRodríguez-dribeas Это недавнее изменение? - person curiousguy; 14.08.2012
comment
@curiousguy: C++03 5.2.8 [expr.typeid] /3, все еще присутствует в C++11. - person David Rodríguez - dribeas; 14.08.2012
comment
[expr.typeid]/2 Если выражение glvalue получено путем применения унарного оператора * к указателю67 и указатель является нулевым значением указателя (4.10), выражение typeid вызывает исключение std::bad_typeid (18.7.3). Выражение glvalue получается путем применения тернарного оператора ?: здесь. - person curiousguy; 14.08.2012
comment
Тип *p и (true? *p : *p) абсолютно одинаков: lvalue в обоих случаях, и стандарт предписывает генерировать исключение. Возможно, вы захотите перечитать свой ответ, потому что ваш комментарий противоречит ему (и согласуется с моим комментарием) - person David Rodríguez - dribeas; 14.08.2012
comment
@DavidRodríguez-dribeas Извините за педантичность: Тип *p и (true? *p : *p) абсолютно одинаков: lvalue в обоих случаях, будучи l/rvalue, не является частью тип выражения. Я не думаю, что есть имя для (типа, lvalueness, bitfieldness) выражения, что является позором. ‹/pedantic› lvalue в обоих случаях я написал наоборот? Возможно, вы захотите перечитать свой ответ Нет, но вы можете перечитать мой предыдущий комментарий. ваш комментарий противоречит ему (и согласуется с моим комментарием) Полная чепуха. Ты выглядишь очень уставшим. - person curiousguy; 14.08.2012
comment
что такое Малоизвестный редко означает сказать по-английски? - person M.M; 24.01.2015
comment
Категория значения @curiousguy — это термин, обозначающий, является ли выражение lvalue и т. д. Возможные категории: {lvalue, xvalue, prvalue}. Существуют также комбинации {glvalue, rvalue}. - person M.M; 24.01.2015

Единственный эффект, который я вижу, заключается в том, что 1 ? X : X дает вам X в качестве rvalue вместо обычного X, которое было бы lvalue. Это может иметь значение для typeid() для таких вещей, как массивы (распадающиеся до указателей), но я не думаю, что это будет иметь значение, если известно, что Derived является классом. Возможно, он был скопирован откуда-то, где rvalue-ness имел значение? Это поддержало бы комментарий о «культовом программировании грузов».

Что касается комментария ниже, я сделал тест и, конечно же, typeid(array) == typeid(1 ? array : array), так что в некотором смысле я ошибаюсь, но мое непонимание все еще может соответствовать недоразумению, которое привело к исходному коду!

person Ben Jackson    schedule 22.07.2011
comment
§5.16/4: Если второй и третий операнды являются lvalue и имеют один и тот же тип, результат имеет этот тип и является lvalue. - person ildjarn; 23.07.2011
comment
Я думаю, что Visual C++ понимает это неправильно (идет копаться в отчетах о проблемах Connect). Ах, вот пример (неправильного) преобразования rvalue с условным оператором: connect.microsoft.com/VisualStudio/feedback/details/279444/ - person Ben Voigt; 23.07.2011

Это поведение описано в [expr.typeid]/2 (N3936):

Когда typeid применяется к выражению glvalue, тип которого является типом полиморфного класса, результат ссылается на объект std::type_info, представляющий тип наиболее производного объекта (то есть динамического типа), на который ссылается glvalue. Если выражение glvalue получено путем применения унарного оператора * к указателю, а указатель является нулевым значением указателя, выражение typeid создает исключение типа, которое соответствует обработчику исключения типа std::bad_typeid.

Выражение 1 ? *p : *p всегда является lvalue. Это связано с тем, что *p является lvalue, а [expr.cond]/4 говорит, что если второй и третий операнд тернарного оператора имеют один и тот же тип и категорию значения, то результат операции также имеет этот тип и категорию значения.

Следовательно, 1 ? *m_basePtr : *m_basePtr — это lvalue с типом Base. Поскольку Base имеет виртуальный деструктор, это тип полиморфного класса.

Следовательно, этот код действительно является примером «Когда typeid применяется к выражению glvalue, тип которого является типом полиморфного класса».


Теперь мы можем прочитать остальную часть приведенной выше цитаты. Выражение glvalue было не "получено путем применения унарного оператора * к указателю" — оно было получено с помощью тернарного оператора. Поэтому стандарт не требует создания исключения, если m_basePtr имеет значение null.

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


Наконец: зачем кому-то это писать? Я думаю, что ответ любопытного парня пока является наиболее правдоподобным предположением: с этой конструкцией компилятору не нужно вставлять проверку нулевого указателя и код для генерации исключения, поэтому это микрооптимизация.

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

person M.M    schedule 24.01.2015

Я подозреваю, что какой-то компилятор был, для простого случая

typeid(*m_basePtr)

возвращает typeid(Base) всегда, независимо от типа среды выполнения. Но превращение его в выражение/temporary/rvalue заставило компилятор выдать RTTI.

Вопрос в том, какой компилятор, когда и т. д. Я думаю, что у GCC были проблемы с typeid на раннем этапе, но это смутное воспоминание.

person Tony    schedule 06.08.2011