Constexpr и endianness

Общим вопросом, возникающим время от времени в мире программирования на C++, является определение конечной даты во время компиляции. Обычно это делается с едва переносимыми #ifdefs. Но имеет ли ключевое слово С++ 11 constexpr наряду с специализацией шаблона лучшее решение для этого?

Будет ли законным С++ 11 делать что-то вроде:

constexpr bool little_endian()
{
   const static unsigned num = 0xAABBCCDD;
   return reinterpret_cast<const unsigned char*> (&num)[0] == 0xDD;
}

И затем специализируйте шаблон для обоих типов endian:

template <bool LittleEndian>
struct Foo 
{
  // .... specialization for little endian
};

template <>
struct Foo<false>
{
  // .... specialization for big endian
};

И затем выполните:

Foo<little_endian()>::do_something();

Ответы

Ответ 1

Предполагая, что N2116 является формулировкой, которая включается, тогда ваш пример плохо сформирован (обратите внимание, что нет понятия "юридический/незаконный" в С++). Предлагаемый текст для [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) могут быть использованы при создании адресное постоянное выражение, но значение объекта не должно быть доступно при использовании этих операторов.

Изменить. Вышеприведенный текст написан на С++ 98. По-видимому, С++ 0x более разрешительный, что позволяет для постоянных выражений. Выражение включает в себя преобразование lvalue-to-rale ссылки на массив, которая запрещена из постоянных выражений, если

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

Мне не ясно, относится ли (&num)[0] к "константной переменной", или относится ли только к литералу num к "такой переменной". Если (&num)[0] относится к этой переменной, тогда неясно, относится ли reinterpret_cast<const unsigned char*> (&num)[0] к <<26 > .

Ответ 2

Я смог написать это:

#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. Если у вас есть какой-либо широкоформатный или среднеспециальный proccesor, пожалуйста, подтвердите, что это работает для вас в комментарии.

Ответ 3

Невозможно определить континент во время компиляции с помощью constexpr. reinterpret_cast явно запрещен [expr.const] p2, как и предложение чтения из неактивного члена объединения.

Ответ 4

Это очень интересный вопрос.

Я не юрист по языку, но вы можете заменить reinterpret_cast союзом.

const union {
    int int_value;
    char char_value[4];
} Endian = { 0xAABBCCDD };

constexpr bool little_endian()
{
   return Endian[0] == 0xDD;
}

Ответ 5

Мой первый пост. Просто хотел поделиться некоторым кодом, который я использую.

//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!");
}

Ответ 6

Это может показаться обманом, но вы всегда можете включить endian.h... BYTE_ORDER == BIG_ENDIAN - это действительный constexpr...

Ответ 7

В грядущем С++ 20 есть std::endian.

#include <type_traits>

constexpr bool little_endian() noexcept
{
    return std::endian::native == std::endian::little;
}

Ответ 8

Простейшим было бы просто изменить приведение в вашей функции:

constexpr bool little_endian()
{
    return 0xDD == (const uint8_t&)0xAABBCCDD;
}

как уже упоминалось ранее.

Ответ 9

Если ваша цель состоит в том, чтобы гарантировать, что компилятор оптимизирует little_endian() в константу true или false во время компиляции, без какого-либо его содержимого, завершающегося в исполняемом файле или выполняющегося во время выполнения, и генерирует только код из "исправьте" один из двух шаблонов Foo, я боюсь, что вас ждет разочарование.

Я также не юрист на языке, но мне кажется, что constexpr похож на inline или register: ключевое слово, которое предупреждает автора компилятора о наличии потенциальной оптимизации. Затем, чтобы разработчик компилятора мог или не воспользоваться этим. Спецификации языка обычно определяют поведение, а не оптимизацию.

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