Делает ли указание constexpr на конструктор автоматически превращает все созданные из него объекты в constexpr?

Вот мой код:

class test{
    public:
    constexpr test(){

    }

    constexpr int operator+(const test& rhs){
        return 1;
    }
};



int main(){

    test t;                         //constexpr word isn't necessary
    constexpr int b = t+test();     // works at compile time!


    int w = 10;                     // ERROR constexpr required
    constexpr int c = w + 2;        // Requires w to be constexpr
    return 0;
}

Я заметил, что это сработало, хотя я не указывал constexpr, Я попытался воспроизвести результат, сделав то же самое с int но я получаю ошибки. В частности, он хочет, чтобы мой int w внутри constexpr int c = w + 2; быть constexpr, С моей первой попытки, которая использует testЭто сработало из-за того, что я использовал constexpr на конструкторе уже? Если это так, то было бы хорошо предположить, что все классы, которые имеют constexpr на их конструкторы приведет к тому, что все объекты, созданные или созданные с его помощью, будут constexpr?

Бонусный вопрос:

Если у меня есть constexpr конструктор, это плохо делать что-то подобное? test * t = new test();?

3 ответа

Решение

Наличие конструктора constexpr не делает объявления этой переменной автоматически constexpr, поэтому t это не constexpr. В этом случае происходит то, что вы вызываете функцию constexpr, такую ​​строку:

constexpr int b = t+test(); 

можно рассматривать следующим образом:

constexpr int b = t.operator+( test() ); 

Итак, вопрос заключается в том, test() является константным выражением, так как оно является конструктором constexpr и не подпадает ни под одно из исключений из черновика стандартного раздела C++11 5.19 [expr.const] параграф 2 который говорит:

Условное выражение является основным константным выражением, если оно не включает одно из следующего в качестве потенциально вычисляемого подвыражения [...]

и включает в себя следующую маркировку:

  • вызов функции, отличной от конструктора constexpr, для литерального класса или функции constexpr [Примечание. Разрешение перегрузки (13.3) применяется как обычно - конец примечания];

[...]

  • вызов конструктора constexpr с аргументами, которые при замене подстановкой вызова функции (7.1.5) не создают все константные выражения для вызовов конструктора и полных выражений в mem-initializer

  • вызов функции constexpr или конструктора constexpr, который превысил бы пределы рекурсии, определенные реализацией (см. Приложение B);

Мы можем увидеть это легче, внеся небольшие изменения в test введя переменную-член x:

class test{
    public:
    constexpr test(){

    }

    constexpr int operator+(const test& rhs) const {
        return x + 1  ;
    }

    int x = 10 ;
};

Попытка получить доступ к нему в operator + и мы видим, что следующая строка теперь не работает:

constexpr int b = t+test();

со следующей ошибкой от clang ( посмотрите вживую):

error: constexpr variable 'b' must be initialized by a constant expression
constexpr int b = t+test();     // works at compile time!
              ^   ~~~~~~~~

note: read of non-constexpr variable 't' is not allowed in a constant expression
    return x + 1  ;
           ^

Это не удается, потому что t не является переменной constexpr, и поэтому его подобъекты также не являются переменными constexpr.

Ваш второй пример:

 constexpr int c = w + 2;  

не работает, потому что подпадает под одно из исключений в проекте стандарта C++11 5.19 [expr.const]:

  • преобразование lvalue в rvalue (4.1), если оно не применяется к

    [...]

    • glvalue целочисленного типа или типа перечисления, которое относится к энергонезависимому константному объекту с предшествующей инициализацией, инициализированной с помощью константного выражения, или

Эффект, который constexpr конструктор по типу класса можно прочитать в стандарте C++

3.9 Типы

(...)

  1. Тип является литеральным типом, если это:

    • это агрегатный тип (8.5.1) или имеет по крайней мере один конструктор constexpr или шаблон конструктора, который не является конструктором копирования или перемещения

(...)

Так constexpr Конструкторы означают, что статическая инициализация может быть выполнена, и возможно использование как это:

#include <iostream>

struct test {
    int val; 
    constexpr test(int val) : val(val) { }
};

template<int N>
struct CC {
    double m[N]; 
};

int main()
{
    CC<test(6).val> k; // usage where compile time constant is required
    std::cout << std::end(k.m) - std::begin(k.m) << std::endl; 
    return 0;
}

Тот факт, что test Это буквальный класс не означает, что все его экземпляры будут постоянными выражениями:

#include <iostream>

struct test {
    int val;
    constexpr test(int val) : val(val) { }
};

int main()
{
    test a(1); 
    ++a.val; 
    std::cout << a.val << std::endl;
    return 0;
}

демонстрация

В приведенном выше примере a не был объявлен как константа, так что даже если a может быть constexpr постоянная, она не одна (следовательно, она может быть изменена).

Ключевое слово constexpr моих экспериментов в этом ответе более или менее указывает компилятору, что он должен иметь возможность статически разрешать все пути кода, указанные в этом вызове. То есть, по крайней мере, прямо сейчас (казалось бы) все должно быть объявлено constexpr вдоль этого кодового пути, иначе это не удастся. Например, в вашем коде начальное присвоение constexpr для b завершится ошибкой, если вы не объявите оператор или конструктор constexpr. Похоже, что constexpr вступает в силу только тогда, когда вы присваиваете переменную, которая объявлена ​​constexpr, в противном случае он, кажется, служит только советчиком для компилятора, что путь кода может быть оптимизирован посредством статической оценки, но это не гарантировано. если вы не указали это явно с помощью присваивания переменной constexpr.

При этом представляется, что объявление конструктора constexpr не оказывает влияния при нормальных обстоятельствах. Приведенный ниже машинный код был создан с использованием следующей командной строки:

g++ -std=c++11 -Wall -g  -c main.cpp -o obj/Debug/main.o
g++  -o bin/Debug/TestProject obj/Debug/main.o  

И вот ваше назначение b производит этот код:

0x4005bd    push   rbp
0x4005be    mov    rbp,rsp
0x4005c1    mov    DWORD PTR [rbp-0x4],0x1
0x4005c8    mov    eax,0x0
0x4005cd    pop    rbp
0x4005ce    ret

Однако, если вы удалите объявление constexpr для переменной b:

0x4005bd    push   rbp
0x4005be    mov    rbp,rsp
0x4005c1    sub    rsp,0x10
0x4005c5    lea    rax,[rbp-0x5]
0x4005c9    mov    rdi,rax
0x4005cc    call   0x4005ee <test::test()>
0x4005d1    lea    rdx,[rbp-0x5]
0x4005d5    lea    rax,[rbp-0x6]
0x4005d9    mov    rsi,rdx
0x4005dc    mov    rdi,rax
0x4005df    call   0x4005f8 <test::operator+(test const&) const>
0x4005e4    mov    DWORD PTR [rbp-0x4],eax
0x4005e7    mov    eax,0x0
0x4005ec    leave
0x4005ed    ret

Кажется, что он обрабатывается так, как если бы оператор и конструктор не были объявлены constexpr, но это действительно ситуация, когда вам следует ознакомиться со спецификой вашего компилятора.

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