Перечисление C++11 с членами класса и оптимизация времени соединения constexpr

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

Насколько я знаю, это невозможно сделать со стандартным классом перечисления MyItem {...}, поэтому для каждого класса перечисления в моем проекте у меня есть вспомогательный класс MyItemEnum, который инкапсулирует эти вспомогательные статические методы, а также создает экземпляры вспомогательных экземпляров сам, так что я могу получить доступ к их методам, чтобы получить дополнительные атрибуты.

Ниже приведен пример (максимально упрощенный, но я считаю, что все обсуждаемые функции остались там).

MyItem.h

enum class MyItem : unsigned int {
    Item1   = 1,
    Item2   = 5
};

class MyItemEnum {
private:
    MyItem myItem;
    size_t extInfo;

    MyItemEnum(const MyItem& myItem, size_t extInfo);
    ~MyItemEnum();
public:
    static MyItemEnum Item1;
    static MyItemEnum Item2;
    static const MyItemEnum &get(MyItem myItem);

    operator MyItem() const;
    size_t getExt() const;
    bool hasNext() const;
    MyItem next() const;
};

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

Мой первый вопрос: подходит ли описанный выше подход, или я должен рассмотреть что-то совершенно другое?

Мой второй вопрос касается оптимизации этого перечисления, которую я пытаюсь сделать с помощью constexpr:

enum class MyItem : unsigned int {
    Item1   = 1,
    Item2   = 5
};

class MyItemEnum {
private:
    MyItem myItem;
    size_t extInfo;

    constexpr MyItemEnum(const MyItem& myItem, size_t extInfo);
public:
    static MyItemEnum Item1;
    static MyItemEnum Item2;
    static constexpr MyItemEnum &get(MyItem myItem);

    constexpr operator MyItem();
    constexpr size_t getExt();
    constexpr bool hasNext();
    constexpr MyItem next();
};

Он компилируется, но, очевидно, constexpr не получает шанс привыкнуть, потому что если я получу доступ:

MyItemEnum::Item1.getExt()

поэтому компилятор не знает, с какими значениями был создан экземпляр Item1.Есть ли вероятность, что приведенное выше выражение будет оценено как constexpr во время оптимизации времени соединения? В качестве альтернативы я мог бы использовать

static constexpr MyItemEnum Item1 = MyItemEnum(MyItem::Item1, 123);

Это активировало бы оптимизации времени компиляции constexpr, но я боюсь, что в некоторых случаях, когда constexpr не может быть оценен во время компиляции, компилятор должен будет создать локальный экземпляр MyItemEnum (вместо использования ссылки на один глобальный объект). статический экземпляр), и я боюсь, что это может привести к снижению производительности (мои реальные перечисления имеют больше атрибутов, чем просто один элемент, поэтому локальная реализация может занять некоторое время?). Это оправданное беспокойство?

2 ответа

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

Итак, используя VS2017 и С++14 (я понимаю, что исходный вопрос был для С++11, но это 2022 год, и у меня уже была такая настройка тестового проекта), мне пришлось изменить код, чтобы добраться до компилировать

          enum class MyItem : unsigned int {
        Item1 = 1,
        Item2 = 5
    };

    class MyItemEnum {
    private:
        MyItem myItem;
        size_t extInfo;

        constexpr MyItemEnum(MyItem myItemIn, size_t extInfoIn)
            : myItem(myItemIn)
            , extInfo(extInfoIn)
        {}
        constexpr MyItemEnum() : MyItemEnum(MyItem::Item1, ~0U) {}
    public:
        static constexpr MyItemEnum getItem1() { return { MyItem::Item1, 1337 }; }
        static constexpr MyItemEnum getItem2() { return { MyItem::Item2, 0xDEAD }; }
        static constexpr MyItemEnum get(MyItem myItem)
        {
            if (myItem == MyItem::Item1) return getItem1();
            if (myItem == MyItem::Item1) return getItem2();

            // error...
            return MyItemEnum{};
        }

        constexpr operator MyItem() const { return myItem; }
        constexpr size_t getExt() const { return extInfo; }
        constexpr bool hasNext() const { return false; }
        constexpr MyItem next() const
        {
            if (myItem == MyItem::Item1) return getItem2();

            // error...
            return MyItemEnum{};
        }
    };

    static_assert(MyItemEnum::getItem1().getExt()==1337, "Expected 1337");
    static_assert(MyItemEnum::getItem2().getExt()==0xDEAD, "Expected 0xDEAD");
  1. Мне пришлось добавить конструктор по умолчанию, так как я не знаю, как вы обрабатывали ошибки.
  2. Мне пришлось изменить статические Item1 и Item2, чтобы они были constexpr и были методами, поскольку статические члены требуют инициализатора в классе, но компилятор не распознал MyItemEnum, жалуясьuse of undefined type 'MyItemEnum'когда он пытался скомпилироватьstatic constexpr MyItemEnum Item1 { MyItem::Item1, 1337 };. Возможно, это не так в С++ 17 или более поздних версиях.
  3. Просто примечание: constexpr подразумевает встроенный
  4. Когда вы используете constexpr, определение уже должно быть в области действия, поэтому ни одно из MyItemEnum не может быть определено в .cpp или определено после callsite, если вы хотите использовать их как constexpr.
  5. С этими изменениями код компилируется, и статические утверждения не дают сбоев.
  6. Даже с constexpr компилятор может решить не оценивать его во время компиляции. Я рекомендую, когда вы беспокоитесь о codegen, проверять сборку до и после. Есть способы заставить компилятор делать что-то во время компиляции, если вы обнаружите, что он не сотрудничает.
  7. Для случаев LTCG вам, возможно, придется проверить сборку в окончательном исполняемом файле, поскольку, по крайней мере, в VC++, .obj содержит недокументированные данные, специфичные для компилятора, и не сворачивается в собственный код до момента компоновки (как следует из названия). ).

У меня нет прямого опыта с использованием constexpr и в результате оптимизации компилятора еще нет, но я могу сказать вам, что просто используя const на членах самого класса или экземпляров будут компиляторы VS2012 и g++ 4.7 выполнять кросс-модульную оптимизацию:

class MyItemEnum {
private:
    // make sure to put const here...
    const MyItem myItem;
    const size_t extInfo;

    MyItemEnum(const MyItem& myItem, size_t extInfo);
    ~MyItemEnum();
public:
    // and put const in here too...
    static const MyItemEnum Item1;
    static const MyItemEnum Item2;
};

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

Я не проверял это на Clang/LLVM, поэтому, если это ваш набор инструментов, я настоятельно рекомендую вам взять этот упрощенный пример и опровергнуть результат самостоятельно. Разборка простых тестовых примеров может быть довольно легко проанализирована, даже если вы не знакомы с языками ассемблера. И в этом случае вы можете скомпилировать две сборки: одну в одном модуле, а другую разбить на два модуля - и сравнить результаты, чтобы убедиться, что LTO выполняет ту работу, которая вам нужна.

Другие вопросы по тегам