Используя clang 3.4 (trunk), есть ли способ рассчитать смещение базового класса с помощью постоянного выражения?
struct A { int a; };
struct B { int b; };
struct C: A, B {};
// cannot access base class of null pointer:
constexpr auto c_b_address = /*(size_t)*/ &(B&) *(C*)nullptr;
Да, Можно рассчитать смещение базового класса с помощью константного выражения, но это совсем не переносимо. Вы можете использовать малоизвестный, но документированный Расширение gcc, которое также поддерживается clang. Это предполагает использование __builtin_constant_p
при использовании с оператором ?:
:
#define CB (&(B&)*(C*)nullptr)
constexpr auto c_b_address = __builtin_constant_p CB ? CB : CB;
Обратите внимание, что я только что использовал макрос CB, чтобы прояснить, что происходит. Конечно, это также можно сделать, повторив выражение несколько раз. Кстати, я впервые узнал об этом трюке в этом вопросе который содержит полезную справочную информацию.
Основная проблема, как вы, наверное, уже поняли, заключается в том, что ни reinterpret_cast
ни эквивалентный бросок в стиле C не допускается в constexpr
выражения. Любопытно, что бросок в стиле C (как указано выше) принят, но reinterpret_cast
(что бы не генерировать код) нет. Я также экспериментировал с неясным, но, казалось бы, уместным ->*
оператор, но со смешанными результатами:
#define CB1 (&(B&)*(C*)nullptr)
#define CB2 (&((reinterpret_cast<C*>(nullptr))->*(&C::b)))
#define CB3 (&(((C*)(nullptr))->*(&C::b)))
#define CB4 (&(B&)*reinterpret_cast<C*>(nullptr))
#define CB CB1
Результаты с g ++ 4.8.3 и clang ++ 3.4:
g++ clang++
--- ------------ --------
CB1 OK OK
CB2 compile error compile error
CB3 OK compiles but gives answer = 0
CB4 compile error compile error
Только на моей 64-битной машине под управлением Linux CB1
дает правильный ответ 4 с обоими компиляторами. С gcc оба CB1
а также CB2
работать с или без __builtin_constant_p
, Используя clang, единственная работающая версия была CB1
с __builtin_constant_p
,
Как @ShafikYaghmour довольно разумно спрашивает в комментарии: «У вас есть ссылка на gcc или clang, которая говорит, что они поддерживают это поведение?» Я собираюсь расширить это, чтобы спросить «какая документация существует, которая указывает, что это преднамеренно, а не просто странный побочный эффект?» В конце концов, если кто-то действительно собирается использовать это, было бы неплохо иметь какое-то указание на то, что оно может продолжать существовать в будущем. Этот раздел пытается решить эту проблему.
Для Clang, ссылка сам исходный код в котором комментарий в VisitConditionalOperator
функция, которая говорит:
// If the condition (ignoring parens) is a __builtin_constant_p call,
// the result is a constant expression if it can be folded without
// side-effects. This is an important GNU extension. See GCC PR38377
// for discussion.
Это, в свою очередь, указывает на Bugzilla GCC ошибка 38377 который обсуждает эту проблему. В частности, в 2008 году эта ошибка была зарегистрирована как «__builtin_constant_p (t)? T: 1 не считается константным целочисленным выражением». В обсуждении отмечается, что для условного оператора (?:
),
Да, это (задокументированный) особый случай, необходимый для совместимости с
существующий код GNU C.
И далее,
Если вы ошиблись в C, то GCC не сможет загрузиться, так как это часть семантики GNU C, на которую GCC полагается при сборке с GCC.
Учитывая это, кажется, что поведение является как конкретным, так и преднамеренным, и поскольку сам gcc полагается на него, вероятно, довольно стабильное поведение.
Тем не менее, применяются все обычные предостережения об использовании нестандартных деталей реализации. Если вы можете выполнить это во время выполнения, это станет приемлемым как для gcc, так и для clang:
ptrdiff_t cb = (char *)(&(B&)*(C*)nullptr) - (char *)nullptr;
Других решений пока нет …