(Realted to this other question of mine; if you give a look at that too, I would really appreciate it.)
If std::array<T,N>::size
is constexpr
, then why does the following code not even compile?
#include <array>
#include <iostream>
constexpr auto print_size = [](auto const& array){
constexpr auto size = array.size();
std::cout << size << '\n';
};
int main() {
print_size(std::array<int,3>{{1,2,3}});
}
The error is the following:
$ g++ -std=c++17 deleteme.cpp && ./a.out
deleteme.cpp: In instantiation of ‘<lambda(const auto:1&)> [with auto:1 = std::array<int, 3>]’:
deleteme.cpp:10:42: required from here
deleteme.cpp:5:20: error: ‘array’ is not a constant expression
5 | constexpr auto size = array.size();
| ^~~~
But I wonder why.
At the lambda call site, the argument is known at compile time, and the lambda should be instantiated with auto
equal to std::array<int,3>
, where 3
is a compile time value, and so should be output of array.size()
.
What is wrong in my reasoning?
I was watching the 2014 Metaprogramming with Boost.Hana: Unifying Boost.Fusion and Boost.MPL presentation, where Louise Dionne touches this topic and explains what @super was telling me in the comments, but I was not understanding it.
This is my rewording of that concept: there's no such a thing as a constexpr
function parameter, therefore whenever the lambda (actually its underlying operator()
) is instantiated for a given type of array
, that single instantiation is the one that should work both for constexpr
and non-constexpr
arguments of that type.
As Louis Dionne says in the linked presentation,
[…] you can't generate a
constexpr
inside a function if it depends on a parameter […] the return type of a function may only depend on the types of its arguments, not on their values […]
This gives a way around the issue. Use array
's type without using array
's value:
constexpr auto print_size = [](auto const& array){
using array_type = decltype(array);
constexpr auto size = array_type{}.size();
std::cout << size << '\n';
};
which I think it's not different, in essence, from what @Jarod42 suggested in a comment:
You might use
constexpr auto size = std::tuple_size<std::decay_t<decltype(array)>>::value
As an addition, I played around a bit more, because a last thing was bugging me: the size of std::array
is not part of the value, but it's part of the type, so why can't I call size
member function in contexpr
s? The reason is that std::array<T,N>::size()
is sadly not static
. If it was, one could call it as in the commented line below (the struct A
is for comparison):
#include <array>
#include <iostream>
#include <type_traits>
template<std::size_t N>
struct A {
static constexpr std::size_t size() noexcept { return N; }
};
constexpr auto print_size = [](auto const& array){
constexpr auto size = std::decay_t<decltype(array)>::size();
std::cout << size << '\n';
};
int main() {
//print_size(std::array<int,3>{{1,2,3}});
print_size(A<3>{});
}