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)
Related
compiler explorer link
Consider the following:
// Variant 1
template<auto> struct require_constexpr;
template<typename R>
constexpr auto is_constexpr_size(R&& r) {
return requires { typename require_constexpr<std::ranges::size(std::forward<R>(r))>; };
}
static_assert(!is_constexpr_size(std::vector{1,2,3,4}));
static_assert(is_constexpr_size(std::array{1,2,3,4}));
The goal here is not the is_constexpr_size function as such, but to find an (requires) expression determining that the size of a range's type is a compile-time constant, so that it can be used in a function taking any range by forwarding-reference in order to if constexpr switch based on it.
Unfortunately this doesn't work since r is of reference type and not usable in a constant expression, although for std::array the call to std::range::sizes will never access the referenced object.
Variant 2: Replacing R&& with R in the function parameter changes this. The constant expression requirements for non-reference type variables are weaker and both MSVC and GCC accept the code with this change, but Clang still doesn't. My understanding is that there is currently a proposal to change the rules, so that the variant with R&& will also work as expected.
However, until this is implemented, I am looking for an alternative, not requiring restriction of the parameter to non-reference types. I also don't want to depend on the range's type being e.g. default-constructible. Therefore I cannot construct a temporary object of the correct type. std::declval is also not usable, since std::ranges::size needs to be evaluated.
I tried the following:
// Variant 3
return requires (std::remove_reference_t<R> s) { typename require_constexpr<std::ranges::size(std::forward<R>(s))>; };
This is accepted by MSVC, but not Clang or GCC. Reading the standard, I am not sure whether this use of a requires parameter is supposed to be allowed.
My questions are as follows:
Regarding std::ranges::size specifically: It takes its argument by forwarding-reference and forwards to some other function. Shouldn't std::ranges::size(r) never be a constant expression (with r a local variable outside the constant expression) for the same reason as in variant 1? If the answer is that it isn't, then assume for the following that std::ranges::size is replaced by a custom implementation not relying on references.
Is my understanding that variant 2 should work correct?
Is variant 3 supposed to work?
If variant 3 is not correct, what is the best way to achieve my goal?
Clarification: That the references are forwarding and that I use std::forward shouldn't be relevant to the question. Maybe I shouldn't have put them there. It is only relevant that the function takes a reference as parameter.
The use case is something like this:
auto transform(auto&& range, auto&& f) {
// Transforms range by applying f to each element
// Returns a `std::array` if `std::range::size(range)` is a constant expression.
// Returns a `std::vector` otherwise.
}
In this application the function would take a forwarding reference, but the check for compile-time constantness shouldn't depend on it. (If it does for some reason I am fine with not supporting such types.)
It is also not relevant to my question that is_constexpr_size is marked constexpr and used in a constant expression. I did so only for the examples to be testable at compile-time. In practice is_constexpr_size/transform would generally not be used in a constant expression, but even with a runtime argument transform should be able to switch return types based on the type of the argument.
If you look closely at the specification of ranges::size in [range.prim.size], except when the type of R is the primitive array type, ranges::size obtains the size of r by calling the size() member function or passing it into a free function.
And since the parameter type of transform() function is reference, ranges::size(r) cannot be used as a constant expression in the function body, this means we can only get the size of r through the type of R, not the object of R.
However, there are not many standard range types that contain size information, such as primitive arrays, std::array, std::span, and some simple range adaptors. So we can define a function to detect whether R is of these types, and extract the size from its type in a corresponding way.
#include <ranges>
#include <array>
#include <span>
template<class>
inline constexpr bool is_std_array = false;
template<class T, std::size_t N>
inline constexpr bool is_std_array<std::array<T, N>> = true;
template<class>
inline constexpr bool is_std_span = false;
template<class T, std::size_t N>
inline constexpr bool is_std_span<std::span<T, N>> = true;
template<auto>
struct require_constant;
template<class R>
constexpr auto get_constexpr_size() {
if constexpr (std::is_bounded_array_v<R>)
return std::extent_v<R>;
else if constexpr (is_std_array<R>)
return std::tuple_size_v<R>;
else if constexpr (is_std_span<R>)
return R::extent;
else if constexpr (std::ranges::sized_range<R> &&
requires { typename require_constant<R::size()>; })
return R::size();
else
return std::dynamic_extent;
}
For the custom range type, I think we can only get its size in a constant expression by determining whether it has a static size() function, which is what the last conditional branch did. It is worth noting that it also applies to ranges::empty_view and ranges::single_view which already have static size() functions.
Once this size detection function is completed, we can use it in the transform() function to try to get the size value in a constant expression, and choose whether to use std::array or std::vector as the return value according to whether the return value is std::dynamic_extent.
template<std::ranges::input_range R, std::copy_constructible F>
constexpr auto transform(R&& r, F f) {
using value_type = std::remove_cvref_t<
std::indirect_result_t<F&, std::ranges::iterator_t<R>>>;
using DR = std::remove_cvref_t<R>;
constexpr auto size = get_constexpr_size<DR>();
if constexpr (size != std::dynamic_extent) {
std::array<value_type, size> arr;
std::ranges::transform(r, arr.begin(), std::move(f));
return arr;
} else {
std::vector<value_type> v;
if constexpr (requires { std::ranges::size(r); })
v.reserve(std::ranges::size(r));
std::ranges::transform(r, std::back_inserter(v), std::move(f));
return v;
}
}
Demo.
C++23 is going to introduce if consteval. Where is this going to be used and how does it differ from constexpr if?
if consteval detects if a constexpr function is called in a constant expression context. The proposal motivates its introduction for the case where one intends to call a consteval function from a constexpr function. To understand what that means we consider the following example.
Let's assume we have a consteval function f:
consteval int f( int i )
{ ... }
f can only be called in a constant expression. On the other hand a constexpr function g can be called either in a constant expression or at run time. That depends on if the arguments to g are known at compile time or not.
Now, calling f from g if g is called at compile time can be done as follows.
constexpr int g( int i )
{
if consteval { //1
return f( i );
}
else {
return fallback();
}
}
Here if consteval in line //1 triggers if g is called in a constant expression.
Note that there must be no condition in //1. Also the braces after if consteval are obligatory.
C++20 introduced is_constant_evaluated for detecting whether a function call occurs within a constant-evaluated context. Using is_constant_evaluated in our example leads to a subtle bug. I.e. exchanging //1 by if constexpr (std::is_constant_evaluated()) { results in is_constant_evaluated to always return true.
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);
Is constexpr an indicator for the compiler or does it mandate a behaviour ?
The example at hand is the following :
template<typename T>
std::size_t constexpr getID() { return typeid(T).hash_code(); }
hash_code is a runtime constant, yet this snippet would compile even though a compile time evaluation is requested with constexpr. Only after the return value is used where a compile time constant is expected, would we get noticed that this is not usable as a constexpr function.
So is constexpr a "hint" (much like the inline keyword) or "a binding request" to the compiler ?
Is constexpr a “hint” (like inline) or “a binding request” to the compiler?
It is neither. Forget about when it is evaluated. Everything (with a few minor exceptions, notably involving volatile) is evaluated whenever the compiler deems it necessary to produce the behaviour of the C++ abstract machine. There isn't much else to say about when things are evaluated.
The compiler is free to produce code that evaluates what would be constant expressions at runtime if that doesn't produce a different behaviour. It is free to produce code that evaluates things not marked constexpr at compile-time if it has the smarts.
If not about compile-time vs runtime, what is constexpr about, then?
constexpr allows things to be treated as constant expressions. Anything marked constexpr must have the possibility of producing a constant expression in some way.
In the case of functions, they can be able to produce constant expressions with some arguments but not others. But as long as there is some set of arguments that can result in a constant expression, a function can be marked constexpr. If such a set of arguments is used in a function call, that expression is a constant expression. Does that mean it is evaluated at compile-time? See above. It's evaluated when the compiler deems appropriate. The only thing it means is that you can use it in a context requiring a constant expression.
For variables, either they are constant expressions or not. They have no arguments, so if constexpr they always have to be initialised with constant expressions.
TL;DR: constexpr is about tagging things as being usable in constant expressions, not about deciding when to evaluate them.
With that out of the way, it appears your function template is ill-formed. There is no set of arguments that could result in a constant expression. The standard doesn't require a diagnostic for this, though.
From the C++11 Wiki page:
If a constexpr function or constructor is called with arguments which
aren't constant expressions, the call behaves as if the function were
not constexpr, and the resulting value is not a constant expression.
Likewise, if the expression in the return statement of a constexpr
function does not evaluate to a constant expression for a particular
invocation, the result is not a constant expression.
The constexpr specifier thus expresses the possibility to evaluate something at compile time and is subject to some restrictions when used.
For your particular snippet it seems to me that the C++11 constraint:
exactly one return statement that contains only literal values,
constexpr variables and functions
is not fulfilled, as hash_code is defined to be:
size_t hash_code() const;
In this case the standard draft n3242 says:
For a constexpr function, if no function argument values exist such
that the function invocation substitution would produce a constant
expression (5.19), the program is ill-formed; no diagnostic required.
I believe your example fits here.
constexpr functions can be used to evaluate compile time constants. So it is possible to use it like:
constexpr int func(int a) { return a+2; }
char x[func(10)];
If func is called during runtime, the compiler can evaluate this expression before if possible. But that is not a must but normally done if the input is also const.
It is also important to have constexpr constructors. This is the only chance to get non POD classes constexpr objects.
class Point
{
private:
int x;
int y;
public:
constexpr Point( int _x, int _y) : x(_x), y(_y) {}
constexpr int GetX() const { return x; }
};
constexpr Point p{1,2};
int main()
{
char i[p.GetX()];
return 0;
}
The complete answer to your question has two aspects:
A constexpr function can only be evaluated at compile-time when all
arguments can be evaluated at compile-time. It can still be used as
a normal function which is evaluated at runtime.
An constexpr variable must be initialized with a value evaluated at compile-time.
The compiler has to raise an error if it cannot do this.
You could assign the hash code to a constexpr variable and then get a compiler output:
#include <typeinfo>
#include <array>
template<typename T>
std::size_t constexpr getID() {
return []() {constexpr size_t id = typeid(T).hash_code(); return id;}(); }
int main() {
// both statement generate compiler errors
//std::array<int, typeid(int).hash_code()> a;
//constexpr size_t y = typeid(int).hash_code();
size_t x = getID<int>();
}
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.)