Compile-time array constants - c++

I seem to be missing something rather fundamental.
I'm trying to use const array members at compile-time.
const int list[3] = { 2, 5, 7 };
const int a = list[2]; // this doesn't error?
template<int N1, int N2>
struct tmax {
enum { value = ((N1 > N2) ? N1 : N2) };
};
const int b = tmax<2,4>::value;
const int c = tmax<list[0],list[1]>::value; // error is here
int main()
{
return 0;
}
Errors:
prog.cpp:10:24: error: 'list' cannot appear in a constant-expression
prog.cpp:10:30: error: an array reference cannot appear in a constant-expression
Here is the relevent IDEOne link
So why doesn't this work? What am I missing? What should I do differently?

Just because an object is const doesn't mean it's a compile time constant expression.
main.cpp:10:20: error: non-type template argument is not a constant expression
const int c = tmax<list[0],list[1]>::value; // error is here
^~~~~~~
main.cpp:10:20: note: read of non-constexpr variable 'list' is not allowed in a constant expression
main.cpp:1:11: note: declared here
const int list[3] = { 2, 5, 7 };
^
This is the reason for constexpr:
constexpr int list[3] = { 2, 5, 7 };
template<int N1, int N2>
struct tmax {
enum { value = ((N1 > N2) ? N1 : N2) };
};
const int b = tmax<2,4>::value;
const int c = tmax<list[0],list[1]>::value; // works fine now
As for why this works:
const int a = list[2]; // this doesn't error?
initializing a const variable doesn't require a constant expression:
int foo(int n) {
const int a = n; // initializing const var with a non-compile time constant

Expressions are not constant expressions if they contain any one of a number of disallowed sub-expressions. One such class of disallowed sub-expressions is:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding
initialization, initialized with a constant expression, or
a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers
to a sub-object of such an object, or
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not
ended, initialized with a constant expression;
In particular, while the name of a const object of enum or intergral type initialized with a constant initializer forms a constant expression (reading its value is what causes the lvalue-to-rvalue conversion), sub-objects of an const aggregate object (such as list in your example, an array) do not, but would if declared constexpr.
const int list[3] = { 2, 5, 7 };
const int a = list[2];
This is valid but a does not constitute a constant expression because it is not initialized with a constant expression.
By changing the declaration of list (we don't have to change the declaration of a), we can make a form a constant expression.
constexpr int list[3] = { 2, 5, 7 };
const int a = list[2];
As list[2] is now a constant expression, a is now a const object of intergral type initialized with a constant expression so a can now be used as a constant expression.

Related

Does the default argument for a function parameter is considered as an initializer for that parameter?

Suppose I have function declarations like these:
static const int R = 0;
static const int I = 0;
void f(const int& r = R);
void g(int i = I);
Per [dcl.fct.default]/1:
If an initializer-clause is specified in a parameter-declaration this
initializer-clause is used as a default argument [..]
and per the grammar constructs, an initializer can comprise an initializer-clause. Right?
So I concluded that R is an initializer for the parameter r, and I is also an initializer for the parameter i.
Now per [const.expr]/2:
A variable or temporary object o is constant-initialized if
(2.1) either it has an initializer [..] and
(2.2) the full-expression of its initialization is a constant expression [..]
So both parameters have an initializer and also the full-expression of their initializations is a constant expression.
So, Are both parameters r and i considered constant-initialized?
No, because an initializer-clause is not necessarily part of an initializer. The grammar item in question here is this version of the parameter-declaration:
attribute-specifier-seqopt thisopt decl-specifier-seq declarator = initializer-clause
Applying just the right amount of hair-splitting, this means that the form of a parameter declaration with a default argument is similar to an initialization, but it is still subtly different. The most obvious difference being that the default argument is ignored if an actual argument is used. Less obvious is that a parameter cannot actually be constant-initialized, even if its default argument is a constant expression. This remains true even if the function is only evaluated at compile time. Here is some code that shows the subtle differences in meaning:
#include <random>
// R may be evaluated at compile time and must be constant-initialized
static constexpr int R = 0;
// f may be evaluated at compile time
constexpr int f(const int& r = R) { return r + 42; }
// I must be constant- *or* zero-initialized, even if only evaluated at runtime
constinit const int I = f();
// g is an "immediate function": it may not be evaluated at runtime
consteval int g(int i = I) { return i - 42; }
int main() {
// A variable that may not appear in constant expressions,
// because it is not constant-initialized
const int not_const = std::rand();
static int x1 = f(); // OK, constant initialization
// constexpr int x2 = f(not_const); // error, not constant-initialized
int x3 = f(); // OK, f() evaluated at compile time
int x4 = f(not_const); // OK, evaluated at runtime
static int y1 = g(); // OK
// constexpr int y2 = g(not_const); // error
int y3 = g(); // OK
// int y4 = g(not_const); // error
}
As you can see here, this generates only one runtime call to f(int const&) and none to g(int). Even though the parameters for the other evaluations were effectively compile-time constants, you can neither use nor specify them as such:
constexpr int f(const int& r = R) {
// constexpr int x = r; // error, not a constant expression
return r + 42;
}
consteval int g(int i = I) {
// constexpr int y = i; // still not a constant expression!
return i - 42;
}
// Also not allowed:
// consteval void h(constexpr int x = 0, constinit int y = 1);

pointer to function as static member in structure

struct X {
int f(int);
static int f(long);
};
int (X::*p1)(int) = &X::f; // OK
int (*p2)(int) = &X::f; // error: mismatch
int (*p3)(long) = &X::f; // OK
int (X::*p4)(long) = &X::f; // error: mismatch
int (X::*p5)(int) = &(X::f); // error: wrong syntax for pointer to member
int (*p6)(long) = &(X::f); // OK
I think that p1 and p5 is the same case. Why is p5 wrong?
Because the standard says so. From the N3936:
5.3.1 Unary operators
A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses. [ Note:
that is, the expression &(qualified-id), where the qualified-id is
enclosed in parentheses, does not form an expression of type “pointer
to member.” Neither does qualified-id, because there is no implicit
conversion from a qualified-id for a non-static member function to the
type “pointer to member function” as there is from an lvalue of
function type to the type “pointer to function” (4.3). Nor is
&unqualified-id a pointer to member, even within the scope of the
unqualified-id’s class. — end note ]
The C++ Standard's definition of the built-in operator & states that only when the parameter to & is a qualified-id, meaning something like Class::Member, does & result in a pointer-to-member. The parentheses make it no longer a qualified-id, so it attempts to parse X::f directly, which is illegal in this context: you're assigning an int (*)(long) to an int (X::*)(int).
The distinction between the two cases resolves an ambiguity. Let's say that you have:
struct X {
int m;
};
struct Y {
int m;
};
struct Z : X, Y {
void F();
};
void Z::F() {
int X::*p1 = &X::m;
int *p2 = &(X::m);
}
Here, &X::m is a pointer-to-member, whereas &(X::m) is an ordinary pointer to int, using the X:: qualification to resolve the ambiguity between X's m and Y's m.

clarification of decltype output when multiplying 2 const ints

int main()
{
const int a = 1;
const int b = 2;
typedef decltype(a*b) multiply_type;
cout << typeid(multiply_type).name() << endl;
return 0;
}
The return value of the program is that multiply_type is int. I'm quite surprised. I expected the type deduction to yield const int and since the expression yields a pr value, the resultant type would be const int.
PS: With auto the return value would be int as it drops the const qualifier.
Any ideas why multiply_type is int instead of const int with decltype ?
Edit: Added an addition example which is also related to cv-qualifier.
#include<iostream>
#include<typeinfo>
using namespace std;
struct Details
{
int m_age;
};
int main()
{
const Details* detail = new Details();
typedef decltype((detail->m_age)) age_type;
cout << typeid(age_type).name() << endl;
int a = 1;
age_type age = a;
age = 10; // This is not possible. Read only.
cout << typeid(age).name() << endl; // This returns the type as int though. Then why is 20 not possble ?
return 0;
}
Edit 2: Check our the link.
http://thbecker.net/articles/auto_and_decltype/section_07.html
`
int x;
const int& crx = x;
/ The type of (cx) is const int. Since (cx) is an lvalue,
// decltype adds a reference to that: cx_with_parens_type
// is const int&.
typedef decltype((cx)) cx_with_parens_type;`
decltype evaluate it argument as it is, decltype(i) where i is cv-qualified lvalue, results in declaring type cv-qualified, but the expression of i*i in decltype(i*i) create a non materialized prvalue with type of i with non cv-qualified, prvalue don't have an explicit notion of constness. your code produce the same as:
using T = const int;
static_assert(is_same<int, decltype(0)>(), "Failed");
The fact that typeid is not showing the cv-qualification is because they a ignored:
5.2.8.5 - If the type of the expression or type-id is a cv-qualified type, the result of the typeid expression refers to a std::type_info object representing the cv-unqualified type.

Why is this not a constexpr?

#include <iostream>
union gc_bits {
size_t value;
struct {
size_t arena : 2;
} bits;
constexpr gc_bits(size_t value_) : value(value_) {
}
};
static constexpr size_t get_max_arenas() {
return gc_bits(~0ULL).bits.arena;
}
size_t current_colour[get_max_arenas()]; // error
int main() {
std::cout << get_max_arenas() << std::endl;
}
The array declaration errors out because get_max_arenas is not a constexpr. I'm not clear on why this should be so.
Slightly rephrasing your program:
static constexpr auto gma = get_max_arenas();
size_t current_colour[gma]; // error
gives the Clang error:
read of member 'bits' of union with active member 'value' is not
allowed in a constant expression
The reason you get this error is that the constructor sets the value, and then you try to read bits. This is not allowed, as commented by #gurka.
Standard quote:
[expr.const]
2 A conditional-expression e is a core constant expression unless the
evaluation of e, following the rules of the abstract machine (1.9),
would evaluate one of the following expressions:
(2.8) — an lvalue-to-rvalue conversion (4.1) or modification (5.18,
5.2.6, 5.3.2) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;

constexpr initializing with pointers

I am trying to initialize a constexpr declaration with a pointer to int which is a const object. I also try to define an object with a object that is not a const type.
Code:
#include <iostream>
int main()
{
constexpr int *np = nullptr; // np is a constant to int that points to null;
int j = 0;
constexpr int i = 42; // type of i is const int
constexpr const int *p = &i; // p is a constant pointer to the const int i;
constexpr int *p1 = &j; // p1 is a constant pointer to the int j;
}
g++ log:
constexpr.cc:8:27: error: ‘& i’ is not a constant expression
constexpr.cc:9:22: error: ‘& j’ is not a constant expression
I believe it is because the objects in main have no fixed addresses, thus g++ is throwing error messages back at me; how would I correct this? Without using literal types.
Make them static to fix their addresses:
int main()
{
constexpr int *np = nullptr; // np is a constant to int that points to null;
static int j = 0;
static constexpr int i = 42; // type of i is const int
constexpr const int *p = &i; // p is a constant pointer to the const int i;
constexpr int *p1 = &j; // p1 is a constant pointer to the int j;
}
This is known as an address constant expression [5.19p3]:
An address constant expression is a prvalue core constant expression
of pointer type that evaluates to the address of an object with static
storage duration, to the address of a function, or to a null pointer
value, or a prvalue core constant expression of type std::nullptr_t.