https://en.cppreference.com/w/cpp/named_req/LiteralType
Here they write that
Literal types are the types of constexpr variables
However it seems to me that I can't define a constexpr string (it doesn't matter if it has static storage duration or is just a stack-allocated constexpr var).
But there are the requirements for literal types:
A literal type is any of the following:
possibly cv-qualified class type that has all of the following properties:
has a [trivial (until C++20) | constexpr (since C++20)] destructor,
is one of
a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor,
Does std::string satisfy these requirements? Or does it maybe fast a non-conformant destructor?
There seems to be no link to the dtor here https://en.cppreference.com/w/cpp/string/basic_string.
I understand that it can't be made fully constexpr (at least for now) because of free store allocation.
I'm just asking whether it is a literal type.
The destructor is constexpr. See the class synopsis. Thus, std::string is a literal type.
Related
In the following code (tested locally and on Wandbox):
#include <iostream>
enum Types
{
A, B, C, D
};
void print(std::initializer_list<Types> types)
{
for (auto type : types)
{
std::cout << type << std::endl;
}
}
int main()
{
constexpr auto const group1 = { A, D };
print(group1);
return 0;
}
MSVC 15.8.5 fails to compile with:
error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'
(all referring to the line containing constexpr)
Clang 8 (HEAD) reports:
error: constexpr variable 'group1' must be initialized by a constant expression
constexpr auto const group1 = { A, D };
^ ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
constexpr auto const group1 = { A, D };
^
gcc 9 (HEAD) reports:
In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
18 | constexpr auto const group1 = { A, D };
| ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
19 | print(group1);
| ^~~~~~
| |
| initializer_list<const Types>
Why?
Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values.
Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print.
Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const.
Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. Granted it is not necessary in this case, but I can't see any good reason for it not to work.
Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang.
(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr, where gcc 9 errors out.)
FWIW, changing the declaration to this:
constexpr auto const group1 = std::array<Types, 2>{ A, D };
Does compile and run on all three compilers. So it is probably the initializer_list itself that is misbehaving rather than the enum values. But the syntax is more annoying. (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.)
constexpr auto const group1 = std::array{ A, D };
Also works, thanks to C++17 template induction. Though now print can't take an initializer_list; it has to be templated on a generic container/iterator concept, which is inconvenient.
When you initialize a std::initializer_list it happens like this:
[dcl.init.list] (emphasis mine)
5 An object of type std::initializer_list is constructed
from an initializer list as if the implementation generated and
materialized a prvalue of type “array of N const E”, where N is the
number of elements in the initializer list. Each element of that array
is copy-initialized with the corresponding element of the initializer
list, and the std::initializer_list object is constructed to
refer to that array. [ Note: A constructor or conversion function
selected for the copy shall be accessible in the context of the
initializer list. — end note ] If a narrowing conversion is required
to initialize any of the elements, the program is ill-formed.
[ Example:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to
this:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an initializer_list
object with a pair of pointers. — end example ]
How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. And that is not a valid constant expression.
[expr.const] (emphasis mine)
5 A constant expression is either a glvalue core constant
expression that refers to an entity that is a permitted result of a
constant expression (as defined below), or a prvalue core constant
expression whose value satisfies the following constraints:
if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a
constant expression,
if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such
an object ([expr.add]), the address of a function, or a null pointer
value, and
if the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if it is an
object with static storage duration that is either not a temporary
object or is a temporary object whose value satisfies the above
constraints, or it is a function.
If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6, when you declare group1 as a static object, clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr.
Ultimately, it's all a bit murky.
It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy).
A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type?
which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object?
I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same.
So there is no explicit requirement that std::initializer_list should be a literal type.
Citations from the final working draft of C++17 (n4659):
[basic.types]/10.5
(10.5) a possibly cv-qualified class type (Clause 12) that has all of the
following properties:
(10.5.1) — it has a trivial destructor,
(10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1),
or has at least one constexpr constructor or constructor template
(possibly inherited (10.3.3) from a base class) that is not a copy or
move constructor,
(10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and
(10.5.4) — if it is not a union, all of its non-static data members and base
classes are of non-volatile literal types.
[initializer_list.syn]/1
An object of type initializer_list provides access to an array of objects of type const E. [ Note:A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. initializer_list is used to implement initializer lists as specified in 11.6.4. Copying an initializer list does not copy the underlying elements. —end note ]
That is the reason why it is not legal to declare a constexpr initializer_list object.
As a question that came up during the discussion of this SO question:
Is it legal, maybe with N3471, to declare a constexpr std::initializer_list object? Example:
constexpr std::initializer_list<int> my_list{};
Why I think it may not be legal: initializer_list would have to be a literal type; but are there any guarantees that it is a literal type?
Citations from N3485.
[dcl.constexpr]/9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized.
literal types requirements, [basic.types]/10, sub-bullet class types:
a class type (Clause 9) that has all of the following properties:
it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19),
it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
all of its non-static data members and base classes are of non-volatile literal types.
Bonus points ;) for answering if
constexpr std::initializer_list<int> my_list = {1,2,3,4,5};
is legal (with references). Though I think this is covered by the above + [dcl.init.list]/5
Update: The situation got a bit more complicated after the resolution of CWG DR 1684 removed the requirement quoted below. Some more information can be found in this discussion on the std-discussion mailing list and in the related question Why isn't `std::initializer_list` defined as a literal type?
[decl.constexpr]/8:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1). [...] The class of which that function is a member shall be a literal type (3.9).
Therefore, the changes of N3471 guarantee std::initializer_list will be a literal type.
Note the constexpr ctor alone doesn't require std::initializer_list to be a literal type, see [dcl.constexpr]/4+8. Side note: An object of non-literal type with constexpr ctor can be initialized during constant initialization [basic.start.init]/2] (part of static initialization, performed before any dynamic initialization).
enum class PARAM_TYPE_ {INT_};
enum class PARAM_NAME_ {NAME_};
typedef std::pair<PARAM_NAME_,PARAM_TYPE_> PARAM_;
static constexpr std::unordered_set<PARAM_> params_ {
PARAM_(PARAM_NAME_::NAME_,PARAM_TYPE_::STRING_)
};
Why is it not possible to put this in my classes header file?
I tried for a long time to figure out why it is not possible to use the combination of:
static, constexpr, non-literal type
But my overall c++ knowledge is just too limited.
From constexpr:
A constexpr variable must satisfy the following requirements:
its type must be a literal type
it must be immediately initialized
the full-expression of its initialization, including all implicit
conversions, constructors calls, etc, must be a constant expression
Now, from literal type we can conclude that a literal type might be an an aggregate type, a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor or, since C++17, a closure type.
From std::unordered_set we see that there are no constexpr constructors. Other two cases are not applicable as well, so you cannot mark std::unordered_set as constexpr.
Basically, you use std::unordered_set with a default allocator which implies dynamic memory allocation. Dynamic memory allocation is a runtime thing when constexpr is a totally compile time beast.
In C++, you can declare many things as constexpr: variables, functions (including member functions and operators), constructors, and since C++1z, also if statements and lambda expressions. However, declaring a destructor constexpr results in an error:
struct X {
constexpr ~X() = default; // error: a destructor cannot be 'constexpr'
};
My questions:
Why can't a destructor be marked constexpr?
If I do not provide a destructor, is the implicitly generated destructor constexpr?
If I declare a defaulted destructor (~X() = default;), is it automatically constexpr?
As per the draft basic.types#10 possibly cv-qualified class type that has all of the following properties:
A possibly cv-qualified class type that has all of the following properties:
(10.5.1) - it has a trivial destructor,
(10.5.2) - it is either a closure type, an aggregate type, or has at
least one constexpr constructor or constructor template (possibly
inherited from a base class) that is not a copy or move constructor,
(10.5.3) - if it is a union, at least one of its non-static data
members is of non-volatile literal type
(10.5.4) - if it is not
a union, all of its non-static data members and base classes are of
non-volatile literal types.
Ques 1: Why a destructor cannot be marked as constexpr?
Because only trivial destructors are qualified for constexpr
Following is the relevant section of the draft
A destructor is trivial if it is not user-provided and if:
(5.4) — the destructor is not virtual,
(5.5) — all of the direct base classes of its class have trivial
destructors, and
(5.6) — for all of the non-static data members of its class that are
of class type (or array thereof), each such class has a trivial
destructor.
Otherwise, the destructor is non-trivial.
Ques 2: If I do not provide a destructor, is the implicitly generated destructor constexpr?
Yes, because implicitly generated destructor is trivial type, so it is qualified for constexpr
Ques 3: If I declare a defaulted destructor (~X() = default;), is it automatically constexpr?
Indeed, this destructor is user-declared and implicitly-generated and thus it is qualified for constexpr.
I'm not able to find any direct reference that only trivial destructors are qualified for constexpr but if the destructor is not trivial then it is for sure that class type is not cv-qualified. So it kind of implicit as you can't define a destructor for cv-qualified class.
C++20 Update
Since C++20, user defined destructors can also be constexpr under certain conditions.
dcl.constexpr/3:
The definition of a constexpr function shall satisfy the following
requirements:
its return type (if any) shall be a literal type;
each of its parameter types shall be a literal type;
it shall not be a coroutine ([dcl.fct.def.coroutine]);
if the function is a constructor or destructor, its class shall not have any
virtual base classes;
its function-body shall not enclose ([stmt.pre])
a goto statement,
an identifier label ([stmt.label]),
a definition of a variable of non-literal type or of static or thread
storage duration.
If what you're looking for is reasoning behind the restriction, have a look at this paper which clearly states that the restriction is artificial - there is no intrinsic property of destructors that prevent them from working in constexpr contexts, and indeed compiler implementors agree that supporting them in constexpr contexts will be trivial to implement.
I guess the C++ standards committee originally placed the restriction in C++11 because they didn't want to deal with destructors at that time and it was easier to just rule them out entirely.
Since C++20, a constructor may be marked constexpr; I don’t know if it says anywhere specifically “a destructor may be constexpr”, but the draft standard includes the following text in section 9.2.5 paragraph 5:
The definition of a constexpr destructor whose function-body is not = delete shall additionally satisfy the
following requirement:
for every subobject of class type or (possibly multi-dimensional) array thereof, that class type shall
have a constexpr destructor.
This also now has a useful function because C++20 also allows new and delete in constexpr contexts, allowing things like vector and string to work at compile time without hacks (although I believe C++20 does not actually include changes to the standard library to allow for this, it is possible to implement something with the same API and behaviour as vector that works completely at compile time).
Why a destructor cannot be marked as constexpr?
The C++11 standard is specific about use of constexpr for consructors and non-static member function. It does not say anything specific about destructor. One may assume that destructors are to be treated as non-static member functions.
constexpr can be used only for const member functions. Since a destructor cannot be const member function, it cannot be qualified as a constexpr member function.
If I do not provide a destructor, is the implicitly generated destructor constexpr.
Since use of
constexpr ~X() = default;
is an error, it makes sense to me that the compiler generated destructor is not a constexpr function. I can't find anything in the standard to justify my statement. I am guessing.
If I declare a defaulted destructor (~X() = default;), is it automatically constexpr
I think not. Once again, I can't find anything in the standard to justify my statement. I am guessing.
FWIW, g++ compiles and builds the following program just fine.
struct X {
constexpr X(int i) : i_(i) {}
~X() = default;
int i_;
};
int main()
{
const X x(10);
}
Reference say's:
constexpr destructors
In most cases, in order to create an object of a type T in a constant
expression, the destruction of T must be trivial. However, non-trivial
destructors are an important component of modern C++, partly due to
widespread usage of the RAII idiom, which is also applicable in
constexpr evaluations. Non-trivial destructors could be supported in
constant expressions, as follows:
Allow destructors to be marked as constexpr
Make defaulted destructors constexpr if they only invoke constexpr destructors
For constexpr variables, require that evaluating the destructor is a constant expression (except that the object being destroyed may be
modified in its own destructor
However, no compelling use cases are known for such a feature, and
there would be a non-trivial implementation cost ensuring that
destructors are run at the right times.
A destructor can't be constexpr because constexpr functions can't have side effects and destructors by definition are only useful through side effects. In short, it would be useless to have a destructor that is constexpr.
A object cannot be constexpr if its destructor is non-trivial. A defaulted one, if trivial, will be considered constexpr
Live
From [class.dtor]
Each decl-specifier of the decl-specifier-seq of a destructor declaration (if any) shall be friend, inline, or virtual.
Missing from it, constexpr. So you could just take it as: because the standard says soTM
As a question that came up during the discussion of this SO question:
Is it legal, maybe with N3471, to declare a constexpr std::initializer_list object? Example:
constexpr std::initializer_list<int> my_list{};
Why I think it may not be legal: initializer_list would have to be a literal type; but are there any guarantees that it is a literal type?
Citations from N3485.
[dcl.constexpr]/9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized.
literal types requirements, [basic.types]/10, sub-bullet class types:
a class type (Clause 9) that has all of the following properties:
it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19),
it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
all of its non-static data members and base classes are of non-volatile literal types.
Bonus points ;) for answering if
constexpr std::initializer_list<int> my_list = {1,2,3,4,5};
is legal (with references). Though I think this is covered by the above + [dcl.init.list]/5
Update: The situation got a bit more complicated after the resolution of CWG DR 1684 removed the requirement quoted below. Some more information can be found in this discussion on the std-discussion mailing list and in the related question Why isn't `std::initializer_list` defined as a literal type?
[decl.constexpr]/8:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1). [...] The class of which that function is a member shall be a literal type (3.9).
Therefore, the changes of N3471 guarantee std::initializer_list will be a literal type.
Note the constexpr ctor alone doesn't require std::initializer_list to be a literal type, see [dcl.constexpr]/4+8. Side note: An object of non-literal type with constexpr ctor can be initialized during constant initialization [basic.start.init]/2] (part of static initialization, performed before any dynamic initialization).