Делает ли указание 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 Типы
(...)
Тип является литеральным типом, если это:
- это агрегатный тип (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, но это действительно ситуация, когда вам следует ознакомиться со спецификой вашего компилятора.