I am very confused by why the following code doesn't compile:
template <typename T, typename... Ts>
void TEST(T&& t, Ts&&... ts) {
if constexpr(sizeof...(ts) > 0) {
TEST(std::forward<Ts>(ts)...);
static_assert( t == 2 , "parameter must be 2");
}
}
template <typename... Ts>
void VARIADIC(Ts&&... ts) {
TEST(std::forward<Ts>(ts)...);
}
int main(int argc, char* argv[]) {
constexpr int a1 = 2;
constexpr int a2 = 2;
VARIADIC(a1, a2);
}
Basically, I am trying to do some compile-time check on the parameters passed to the function VARIADIC. However, the compiler complained about the following:
error: non-constant condition for static assertion
static_assert( t == 2 , "parameter must be 2");
^~~~~~~~~~~~~
error: ‘t’ is not a constant expression
It is very obvious the given parameters a1 and a2 are constants, and there must be some ways to perform the check on variadic arguments.
The function arguments are not core constant expressions according to 5.20 [expr.const] paragraph 2.9: variables or references are only core constant expressions if they are initialized with a constant expression or their live-time began within the definition of constant expression. Function arguments clearly satisfy neither.
The background is that constexpr functions can be called with run-time values. For the determination of whether a variable is a constant expression within the function it is immaterial whether it is actually called with a constant expression. Whether it matter whether the call is with a constant expression is when the function is evaluated: although the arguments do not behave as constant expressions within the function the result can still be a constant expression.
Related
I have code something like this:
template<typename ... Args>
constexpr size_t get_init_size(Args ... args) {
return sizeof...(Args);
}
template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
constexpr size_t header_lenght = get_init_size(args...);
return header_lenght;
}
constexpr auto create_ipv4_header() {
constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
return x;
}
I know it is dummy code, but I isolate it to find error.
Compiler give me error(GCC):
In instantiation of 'constexpr auto make_generic_header(Args&& ...) [with Args = {int, int, int}]':
/tmp/tmp.CaO5YHcqd8/network.h:39:43: required from here
/tmp/tmp.CaO5YHcqd8/network.h:31:22: error: 'args#0' is not a constant expression
31 | constexpr size_t header_lenght = get_init_size(args...);
| ^~~~~~~~~~~~~
I tried add qualifier const to function parameters but it same doesn't work. In theory all this function can calculate in compile time. But where is problem I can't find with my knowledge.
constexpr means different things for variables vs. functions.
For variables, it means that the variable must be compile-time. Therefore, it needs to be initialized with a constant expression.
For functions, constexpr means the function can be run at compile-time in addition to runtime using the same code inside. Therefore, everything you do inside must be applicable for a runtime call as well.
With that in mind, let's study make_generic_header:
template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
constexpr size_t header_lenght = get_init_size(args...);
return header_lenght;
}
Here, header_lenght is a constexpr variable, so must be compile-time. Therefore, get_init_size(args...) must be done at compile-time as well. However, parameters are not constant expressions for various reasons, so this won't work. If this did work, it would mean that make_generic_header, a constexpr function, is unusable at runtime¹, which doesn't fit its requirement of being usable at both compile-time and runtime.
The fix is rather simple: Use code that works in both cases:
template<typename ... Args>
constexpr auto make_generic_header(Args ... args) {
size_t header_lenght = get_init_size(args...);
return header_lenght;
}
Did you spot it? The only change is to remove constexpr from header_lenght. If this function is run at compile-time, it will still work out and the overall function call expression will be a constant expression.
¹"But it won't work in consteval either!" - True, the reasoning given in the answer is sufficient for constexpr, but I've left out the more fundamental reason since it's not relevant here.
It's not about the reference problem. And constexpr variable and constexpr function are different things. Quoted from the answer https://stackoverflow.com/a/31720324:
The reference does not have a preceding initialization from the point of view of i, though: It's a parameter. It's initialized once ByReference is called.
This is fine, since f does have preceding initialization. The initializer of f is a constant expression as well, since the implicitly declared default constructor is constexpr in this case (§12.1/5).
Hence i is initialized by a constant expression and the invocation is a constant expression itself.
And about "preceding initialization", quoted from this:
It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.
Corresponding to your case, the line:
constexpr size_t header_length = get_init_size(args...);
From the point of view of header_length, get_init_size(args...) is not a core constant expression, since its invoke argument is from the function's argument, and args is not a core constant expression either.
Simple modification can make your code work, you can remove the constexpr qualifier from header_length, or directly return get_init_size(args...); in make_generic_header's function body.
I hope this and this might also help you.
I rewrite the code that will be work and I think will be execute in compile time:
template<typename ... Args>
constexpr auto make_generic_header(const Args ... args) {
std::integral_constant<size_t, sizeof...(Args)> header_lenght;
return header_lenght.value;
}
constexpr auto create_ipv4_header() {
constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
return x;
}
I removed function get_init_size and used code part of template parameter(It is guaranteed that it is will be execute on compile time) and after return the number of arguments passed to function(For std::integral_constant for all object value same and know on compile time)
Consider the following code:
template<int value>
constexpr int foo = value;
template<typename... Ts>
constexpr int sum(Ts... args) {
return foo<(args + ...)>;
}
int main() {
static_assert(sum(10, 1) == 11);
}
clang 4.0.1 gives me the following error:
main.cpp:6:17: error: non-type template argument is not a constant expression
return foo<(args + ...)>;
^~~~
This surprised me. Every argument is known at compile time, sum is marked as constexpr, so I see no reason why the fold expression can't be evaluated at compile time.
Naturally, this also fails with the same error message:
constexpr int result = (args + ...); // in sum
[expr.prim.fold] isn't very helpful, it's very short and only describes the syntax allowed.
Trying out newer versions of clang also gives the same result, as does gcc.
Are they actually allowed or not?
A constant expression is allowed to contain a fold expression. It is not allowed to use the value of a function parameter, unless the function call is itself part of the entire constant expression. By way of example:
constexpr int foo(int x) {
// bar<x>(); // ill-formed
return x; // ok
}
constexpr int y = foo(42);
The variable y needs to be initialized with a constant expression. foo(42) is an acceptable constant expression because even though calling foo(42) involves performing an lvalue-to-rvalue conversion on the parameter x in order to return its value, that parameter was created within the entire constant expression foo(42) so its value is statically known. But x itself is not a constant expression within foo. An expression which is not a constant expression in the context where it occurs can nevertheless be part of a larger constant expression.
The argument to a non-type template parameter must be a constant expression in and of itself, but x is not. So the commented-out line is ill-formed.
Likewise your (args + ...) fails to be a constant expression (and hence cannot be used as a template argument) since it performs lvalue-to-rvalue conversion on the parameters of sum. However, if the function sum is called with constant expression arguments, the function call as a whole can be a constant expression even if (args + ...) appears within it.
Some readers of this question might be interested in knowing how OP:s example could be modified in order to compile and run as expected, hence I'm including this addendum to the Brian:s excellent accepted answer.
As Brian describes, the value of the variadic function parameter is not a constant expression within sum (but will not cause foo to not be a constant expression as long as the parameter doesn't "escape" the scope of foo; as it has been created within the constant expression foo(42)).
To apply this knowledge to OP:s example, instead of using a variadic function parameter that will not be treated as a constexpr when escaping the constexpr immediate scope of sum, we may migrate the variadic function parameter to be a variadic non-type template parameter.
template<auto value>
constexpr auto foo = value;
template<auto... args>
constexpr auto sum() {
return foo<(args + ...)>;
}
int main() {
static_assert(sum<10, 1, 3>() == 14);
}
Your problem is unrelated to ....
template<class T0, class T1>
constexpr int sum(T0 t0, T1 t1) {
return foo<(t0+t1)>;
}
this also fails in the same way.
Your problem is, in essence, that a constexpr function must be callable with non-constexpr arguments.
It is a common misunderstanding of what constexpr means: it doesn't mean "always constexpr".
There are complex standard clauses saying what goes wrong here, but the essence is that within a constexpr function, the function arguments themselves are not considered constexpr. The result of the function can be if the inputs are, but within the function the code must be valid even if the arguments are not constexpr.
You can still work around this: user define a literal ""_k that parses the integer and generates an integral_constant.
static_assert(sum(10_k, 1_k) == 11);
would compile and run, because + on integral constants doesn't depend on the variables being constexpr. Or you can take the values as non-type template parameters.
static_assert(sum<10, 1>() == 11);
In C++ Primer, Fifth Edition, §6.5.2:
A constexpr function is defined like any other function but must meet certain restrictions: The return type and the type of each parameter in must be a literal type (§2.4.4, p. 66), and the function body must contain exactly one return statement
but another sentence in this chapter (page 239):
A constexpr function is permitted to return a value that is not a constant
// scale(arg) is a constant expression if arg is a constant expression
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
Is it a contradictory summary? I am confused about it.
The return type of scale is literal type?
update:
what's the difference between literal type and constant ?
First of all what I believe the author meant was that a constexpr function does not have to result in a constant expression, which is an expression that can be evaluated at compile time.
A constexpr function will only yield a constant expression if the arguments to the function are also constant expressions and the comment right after says exactly that:
// scale(arg) is a constant expression if arg is a constant expression
and the examples that follow right after also demonstrate this behavior:
int arr[scale(2)]; // ok: scale(2) is a constant expression
int i = 2; // i is not a constant expression
int a2[scale(i)]; // error: scale(i) is not a constant expression
In C++(as opposed to C99) as array size must be a constant expression and so the last case is an error since the argument to scale is not a constant expression.
This is different concept from the return type of the function, which must be a literal type which is any of the following:
void(since c++14) (so that constexpr functions can return void)
scalar type which includes Arithmetic types, enumeration types, pointer types, pointer to member types, std::nullptr_-
t, and cv-qualified versions of these types)
reference type
an array of literal type
class type that has all of the following properties:
has a trivial destructor,
is either
an aggregate type
a type with at least one constexpr (possibly template) constructor that is not a copy or move constructor
all non-static data members and base classes are of non-volatile literal types.
It's not contradictory. As well as mandating that the return type must be of "literal type", the draft standard states that a call to a constexpr function does not have to appear in a constant expression. From the C++11 draft standard:
§7.1.5/7 A call to a constexpr function produces the same result as
a call to a equivalent non-constexpr function in all respects
except that a call to a constexpr function can appear in a constant
expression.
constexpr does nothing but tells the compiler that the value is there in compile time, so you can use it as template argument (for example)
int a1 = 5;
std::array<int, a1> arr1; // error, a is variable
const int a2 = 5;
std::array<int, a2> arr2; // OK
int f1() { return 3; }
std::array<int, f1()> arr3; // error, compiler doesn't know it is const 3
constexpr int f2() { return 3; }
std::array<int, f2()> arr4; // OK
Later you also can:
constexpr int f3() { return f1() + 1; } // error, f1 is not constexpr
constexpr int f4() { return f2() + 1; } // OK
std::array<int, f4()> arr5; // OK
Now about literal types limitation: the function arguments and result types should be literal types (Need clarification on definition of literal type), the very same limitation as template arguments apply (known in compile types).
constexpr std::string f5() { return "hello"; } // error,
// std::string is not literal type
constexpr const std::string& f6() {
static const std::string s = "hello";
return s;
}
template<const std::string& s> SomeClass { ... };
SomeClass<f6()> someObject;
Can someone please explain why the marked line below compiles fine:
template<typename T, int N>
constexpr
int get_size(T (&)[N])
{
return N;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Intuitively to me, get_size(xs) isn't a constant expression because xs itself isn't so I don't understand why it works.
After the template function is instantiated your program becomes equivalent to the following:
constexpr
int get_size(int (&)[10])
{
return 10;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Then after function invocation substitution it becomes equivalent to the following:
int main()
{
int xs[10];
constexpr int y = 10; // HERE.
static_assert(10 == y, "wrong size");
}
Function invocation substitution is described under 7.1.5 [dcl.constexpr]/5. Essentially parameters are replaces as if copy-initialized and then subsituted for occurences in the return expression. The return expression then likewise as-if copy-initializes the return value. The resulting expression then becomes the expression that is subsituted for the function call. Only after this is the expression considered if it satisfies the constraints on constant expressions placed by the context. (Note, a quality compiler can of course determine that the constexpr function can never be used successfully after any such operation, and can fail after encounting the function definition, but it doesn't have to)
Also note, just to confuse you this concept is removed in C++14 and replaced with a different concept for how constexpr functions are evaluated. Among other things you will be able to use if statements, for statements and local variables of literal type within constexpr functions.
Your question and the comment:
I guess I'm confused why an automatic variable whose address isn't known can be passed by reference to a function used in a constant expression
When the compiler sees get_size(xs), it has already parsed the previous line which is int xs[10]; and thus knows the type and size of xs. There is nothing going to change at runtime, as far the type and the size is concerned — and these two are the information required by the compile in order to instantiate the function template, so it doesn't face any problem instantiating the function template, which in this case behaves as constexpr because everything is known at compile-time, which is why the static_assert doesn't fail.
I tried to find a solution for the problem of the question C++ template non-type parameter type deduction, which does not involve a template parameter to call f, but implicitly chooses the correct type for the template parameter.
Since constexpr should guarantee that a function only contains compile time constants, and is evaluated at compile time (at least thats what i think it does), i thought it might be the solution for this issue.
So i came up with this:
template <class T, T VALUE> void f() {}
//first i tried this:
template <class T> auto get_f(T t) -> decltype( &f<T,t> ) { return f<T,t>; }
//second try:
template <class T> constexpr void (&get_f( T t ))() { return f<T,t>; }
int main()
{
get_f(10)(); //gets correct f and calls it
}
first version generates following error:
error: use of parameter 't' outside function body
which is really confusing, since the usage of parameters in the decltype statement of a trailing return type should be ok?
second version generates following error:
error: invalid initialization of non-const reference of type 'void (&)()'
from an rvalue of type '<unresolved overloaded function type>'
which is kinda confusing, since i fully qualified f in get_f.
I would expect this kind of error messages if i did not have the constexpr. So do i have a false understanding of what constexpr does, or is the C++0x implementation of GCC flawed for this case ?
I am using GCC 4.6.2
Since constexpr should guarantee that a function only contains compile
time constants, and is evaluated at compile time (at least thats what
i think it does), i thought it might be the solution for this issue.
A constexpr function can be used in a constant expression context, but is not restricted to one. In this respect they are different from a metafunction and a regular function. Consider the problem of returning the successor of an integer:
// Regular function
int f(int i)
{ return i + 1; }
// Regular metafunction
template<int I>
struct g {
static constexpr auto value = I + 1;
};
// constexpr function
constexpr int h(int i)
{ return i + 1; }
// Then...
{
// runtime context: the metafunction can't be used
int i;
std::cin >> i;
f(i); // Okay
g<i>::value; // Invalid
h(i); // Okay
// compile time context: the regular function can't be used
char a[f(42)]; // Invalid
char b[g<42>::value]; // Okay
char c[h(42)]; // Okay
}
constexpr has other usages (e.g. constructors) but when it comes to constexpr functions this is the gist of it: some functions should be available in both runtime and constant contexts because some computations are available in both. It's possible to compute i + 1 whether i is a compile-time constant or is extracted from std::cin.
This means that inside the body of a constexpr function the parameters are not themselves constant expressions. So what you are attempting is not possible. Your function can't deal with
int i;
std::cin >> i;
get_f(i); // what's the return type?
and the violation happens here:
constexpr auto get_f(T t)
-> decltype( &f<T,t> ) // <-
Since t is not a constant expression according to the rules of the language (no matter what, even if you actually only pass constant expressions in), it can't appear as the second template parameter of f.
(And in the larger picture it means that no, you can't use argument deduction from function templates to conveniently pass a non-type parameter to a class template.)