Существуют ли расширения, позволяющие оптимизаторам предполагать, что параметры const-ref останутся постоянными?

Связанный с моим прежним вопросом: компиляторам не разрешено предполагать, что параметры const-ref останутся const?

Мой новый вопрос: есть ли специфические для компилятора, нестандартные расширения или синтаксис, чтобы сообщить GCC/Clang/MSVC, что объект не доступен для записи? Например, здесь некоторый фальшивый код, который я хотел бы написать:

void f(const int& i) {

    // At this point, compiler doesn't know if "i" can be mutated or not,
    // so it assumes it can

    // Fake-ish -- compiler now assumes "i" cannot be mutated and optimizes accordingly
    __assume(readonly i);

    // ...

}

Ответ 1

Если i предполагается содержать const для всей функции, а f() не имеет побочных эффектов, вы можете объявить ее с __attribute__((pure)):

int f(const int&) __attribute__((pure));

Обратите внимание, что pure функция не имеет смысла возвращать void, поэтому я изменил ее на int.

Хотя это не влияет на компиляцию f(), это влияет на функции, вызывающие его (проверьте его на godbolt):

#include <iostream>

int f(const int& i) __attribute__((pure));

int main() {
    int i = 40;
    f(i);
    if (i != 40) {
        std::cout << "not 40" << std::endl;
    }
}

Здесь __attribute__((pure)) сообщает компилятору, что f() не изменит i, поэтому компилятор не будет генерировать вызов std::cout <<...

Без __attribute__((pure)), даже если f() объявлен для принятия параметра const int& i, компилятор должен предположить, что значение i может измениться и сгенерировать if и вызов для std::cout <<...

Ответ 2

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

void f(const int&);

struct ImmutableInt {
    // it is UB to change val, because it is itself const
    // not just a const reference
    const int val;    
};

void g3(const ImmutableInt & i) {
    if (i.val == 42) f(i.val);
    if (i.val == 42) f(i.val); // the compiler could assume i.val has not changed
}

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

Я не думаю, что есть фундаментальные проблемы с этим, потому что clang использует похожие и более сложные рассуждения о указателе виртуальной таблицы для достижения девиртуализации. Это рассуждение несколько усложняется, потому что vptr будет меняться во время построения и, следовательно, не полностью const. Метаданные llvm, используемые для этого, называются инвариантными.group, но я не знаю, можете ли вы установить его самостоятельно в C++.