constexpr и endianness
Общий вопрос, который время от времени возникает в мире программирования на C++, - это определение порядка байтов во время компиляции. Обычно это делается с помощью только переносимых #ifdefs. Но разве С ++11 constexpr
Ключевое слово вместе со специализацией шаблона предлагают нам лучшее решение для этого?
Было бы законно C++11 сделать что-то вроде:
constexpr bool little_endian()
{
const static unsigned num = 0xAABBCCDD;
return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD;
}
А затем специализируем шаблон для обоих типов байтов:
template <bool LittleEndian>
struct Foo
{
// .... specialization for little endian
};
template <>
struct Foo<false>
{
// .... specialization for big endian
};
А затем сделайте:
Foo<little_endian()>::do_something();
9 ответов
Если предположить, что N2116 является формулировкой, которая включена, то ваш пример неверен (обратите внимание, что в C++ отсутствует понятие "легальный / нелегальный"). Предлагаемый текст для [decl.constexpr]/3 говорит
- его тело-функция должно быть составным оператором вида
{ return expression; }
где выражение - это потенциальное постоянное выражение (5.19);
Ваша функция нарушает требование в том, что она также объявляет локальную переменную.
Изменить: это ограничение может быть преодолено путем перемещения Num за пределы функции. Функция все равно не будет правильно сформирована, потому что выражение должно быть потенциальным константным выражением, которое определяется как
Выражение является потенциальным постоянным выражением, если оно является постоянным выражением, когда все вхождения параметров функции заменяются произвольными постоянными выражениями соответствующего типа.
IOW, reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD
должно быть постоянным выражением. Однако это не так: &num
будет константным выражением адреса (5.19/4). Однако доступ к значению такого указателя не разрешен для константного выражения:
Подписной оператор [] и доступ к членам класса. и операторы,
&
а также*
унарные операторы и приведение указателей (кроме dynamic_casts, 5.2.7) могут использоваться при создании выражения константы адреса, но использование объекта не должно быть доступно для значения объекта.
Изменить: приведенный выше текст из C++98. По-видимому, C++0x более разрешительный, чем допускает константные выражения. Выражение включает преобразование ссылки на массив из lvalue в rvalue, которое запрещено для константных выражений, если только
он применяется к lvalue эффективного целочисленного типа, который относится к энергонезависимой переменной const или статическому члену данных, инициализированному с помощью константных выражений
Мне не понятно (&num)[0]
"относится к" константной переменной, или только ли литерал num
"относится к" такой переменной. Если (&num)[0]
относится к этой переменной, тогда неясно, reinterpret_cast<const unsigned char*> (&num)[0]
до сих пор "относится к" num
,
Я был в состоянии написать это:
#include <cstdint>
class Endian
{
private:
static constexpr uint32_t uint32_ = 0x01020304;
static constexpr uint8_t magic_ = (const uint8_t&)uint32_;
public:
static constexpr bool little = magic_ == 0x04;
static constexpr bool middle = magic_ == 0x02;
static constexpr bool big = magic_ == 0x01;
static_assert(little || middle || big, "Cannot determine endianness!");
private:
Endian() = delete;
};
Я протестировал его с g++, и он компилируется без предупреждений. Это дает правильный результат на x64. Если у вас есть обработчик с прямым или средним порядком байтов, пожалуйста, подтвердите, что это работает для вас в комментарии.
Невозможно определить порядок байтов во время компиляции, используя constexpr
, reinterpret_cast
явно запрещен [expr.const]p2, как и предложение iain о чтении от неактивного члена объединения.
Есть std::endian
в будущем C++20.
#include <type_traits>
constexpr bool little_endian() noexcept
{
return std::endian::native == std::endian::little;
}
Мой первый пост. Просто хотел поделиться кодом, который я использую.
//Some handy defines magic, thanks overflow
#define IS_LITTLE_ENDIAN ('ABCD'==0x41424344UL) //41 42 43 44 = 'ABCD' hex ASCII code
#define IS_BIG_ENDIAN ('ABCD'==0x44434241UL) //44 43 42 41 = 'DCBA' hex ASCII code
#define IS_UNKNOWN_ENDIAN (IS_LITTLE_ENDIAN == IS_BIG_ENDIAN)
//Next in code...
struct Quad
{
union
{
#if IS_LITTLE_ENDIAN
struct { std::uint8_t b0, b1, b2, b3; };
#elif IS_BIG_ENDIAN
struct { std::uint8_t b3, b2, b1, b0; };
#elif IS_UNKNOWN_ENDIAN
#error "Endianness not implemented!"
#endif
std::uint32_t dword;
};
};
Версия Constexpr:
namespace Endian
{
namespace Impl //Private
{
//41 42 43 44 = 'ABCD' hex ASCII code
static constexpr std::uint32_t LITTLE_{ 0x41424344u };
//44 43 42 41 = 'DCBA' hex ASCII code
static constexpr std::uint32_t BIG_{ 0x44434241u };
//Converts chars to uint32 on current platform
static constexpr std::uint32_t NATIVE_{ 'ABCD' };
}
//Public
enum class Type : size_t { UNKNOWN, LITTLE, BIG };
//Compare
static constexpr bool IS_LITTLE = Impl::NATIVE_ == Impl::LITTLE_;
static constexpr bool IS_BIG = Impl::NATIVE_ == Impl::BIG_;
static constexpr bool IS_UNKNOWN = IS_LITTLE == IS_BIG;
//Endian type on current platform
static constexpr Type NATIVE_TYPE = IS_LITTLE ? Type::LITTLE : IS_BIG ? Type::BIG : Type::UNKNOWN;
//Uncomment for test.
//static_assert(!IS_LITTLE, "This platform has little endian.");
//static_assert(!IS_BIG_ENDIAN, "This platform has big endian.");
//static_assert(!IS_UNKNOWN, "Error: Unsupported endian!");
}
Это очень интересный вопрос.
Я не являюсь юристом по языку, но вы можете заменить reinterpret_cast объединением.
const union {
int int_value;
char char_value[4];
} Endian = { 0xAABBCCDD };
constexpr bool little_endian()
{
return Endian[0] == 0xDD;
}
Вот простая версия, совместимая с С++11, вдохновленная ответом @no-name :
constexpr bool is_system_little_endian(int value = 1) {
return static_cast<const unsigned char&>(value) == 1;
}
Использование значения по умолчанию для запуска всего в одной строке соответствует требованиям C++11 к функциям: они должны содержать только один оператор возврата.
Хорошо делать это (и тестировать!)constexpr
контекст заключается в том, что он гарантирует, что в коде нет неопределенного поведения.
В проводнике компилятора здесь .
Это может показаться обманом, но вы всегда можете включить endian.h... BYTE_ORDER == BIG_ENDIAN является действительным constexpr...
Если ваша цель - убедиться, что компилятор оптимизирует little_endian()
в константу истина или ложь во время компиляции, без какого-либо его содержимого, которое заканчивается в исполняемом файле или выполняется во время выполнения, и генерирует только код из "правильного" одного из двух Foo
шаблоны, боюсь, вас ждет разочарование.
Я также не адвокат по языку, но мне это кажется constexpr
как inline
или же register
: ключевое слово, которое предупреждает автора компилятора о потенциальной оптимизации. Тогда разработчик компилятора может воспользоваться этим или нет. Языковые спецификации обычно требуют поведения, а не оптимизации.
Кроме того, вы действительно пробовали это на различных компиляторах жалоб C++0x, чтобы увидеть, что происходит? Я предполагаю, что большинство из них задохнется от ваших двойных шаблонов, так как они не смогут понять, какой из них использовать при вызове с false
,