Указывает ли 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;
}
Я заметил, что он работал, хотя я не указывал test constexpr
. Я попытался реплицировать результат, выполнив то же самое с int
, но я получаю ошибки. В частности, он хочет, чтобы мой int w
внутри constexpr int c = w + 2;
был constexpr
. С моей первой попытки, которая использует test
, она работала из-за причины, по которой я уже использовал constexpr
в конструкторе? Если это так, то было бы неплохо предположить, что все классы, имеющие constexpr
в своих конструкторах, приведут к тому, что все объекты будут созданы или созданы с ним как constexpr
?
Бонусный вопрос:
Если у меня есть конструктор constexpr
, плохо ли что-то делать? test * t = new test();
?
Ответы
Ответ 1
Наличие конструктора constexpr не делает объявления этой переменной автоматически constexpr, поэтому t
не является constexpr. В этом случае происходит то, что вы вызываете функцию constexpr, эта строка:
constexpr int b = t+test();
можно посмотреть следующим образом:
constexpr int b = t.operator+( test() );
Итак, вопрос в том, является ли test()
константным выражением, так как конструктор является constexpr и не подпадает ни под одно из исключений согласно черновому стандартному разделу С++ 11 5.19
[expr.const ] параграф 2
, в котором говорится:
Условное выражение является основным константным выражением, если оно включает в себя одно из следующего в качестве потенциально оцениваемого подвыражения [...]
и включает в себя следующую маркировку:
-
вызов функции, отличной от конструктора constexpr для литерального класса или функции constexpr [Примечание: разрешение перегрузки (13.3) применяется как обычно - конец примечания];
[...]
вызов конструктора constexpr с аргументами, которые при замене вызовом функции подстановка (7.1.5), не производят все константные выражения для вызовов конструктора и полные выражения в mem-инициализаторах
вызов функции 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;
не работает, поскольку подпадает под одно из исключений в черновом стандартном разделе С++ 11 5.19
[expr.const]:
Ответ 2
Влияние, которое конструктор 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;
}
Demo
В приведенном выше примере экземпляр a
не был объявлен как константа, поэтому, хотя a
может быть константой constexpr
, она не одна (следовательно, она может быть изменена).
Ответ 3
Ключевое слово 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, но это ситуация, когда вам следует проконсультироваться с особенностями вашего компилятора.