The wording of the question title is probably incorrect and I'll fix it happily from your suggestions.
My question can be illustrated by this snippet:
#include <array>
template <typename Callable>
constexpr auto make_array_ok(Callable callable) {
return std::array<int, callable()>{};
};
// constexpr auto make_array_bad(std::size_t s)
// {
// return std::array<int,s>{};
// };
// straightforward constexpr function returning its arg
template <std::size_t S>
constexpr std::size_t foo() {
return S;
}
int main(int argc, char**) {
static_cast<void>(argc);
auto size = []() { return std::size_t{42}; };
// fails to compile -- as I expected
// auto size_dyn = [argc]() { return std::size_t(argc); };
// [[maybe_unused]] auto a = make_array_ok(size_dyn);
// also fails to compile -- but why?
[[maybe_unused]] auto size_capt = [arg = size()]() { return arg; };
// [[maybe_unused]] auto b = make_array_ok(size_capt);
// also fails to compile -- but why?
[[maybe_unused]] constexpr auto size_capt_ce = [arg = size()]() {
return arg;
};
// [[maybe_unused]] auto c = make_array_ok(size_capt_ce);
// direct usage also fails to compile -- is it because the closure is not
// constexpr? auto d = std::array<int,size_capt()>{}; direct usage compiles
// when the closure is constexpr
[[maybe_unused]] auto e = std::array<int, size_capt_ce()>{};
// Simpler exemple with a constexpr function instead of a lambda closure.
[[maybe_unused]] auto f = std::array<int, foo<42>()>{};
// calling the constexpr function through a function fails to compile - but
// why?
// [[maybe_unused]] auto g = make_array_ok(foo<42>);
// compiles with captureless lambda
[[maybe_unused]] auto h = make_array_ok(size);
return size_capt_ce();
}
It's quite common that constexpr function arguments are considered as not usable where a constant expression is expected (as constexpr function can be called at runtime, with runtime argument), so the failure of make_array_bad to compile is expected. But under what rules can make_array_ok compile (when used with the lambda returning 42)?
I'm suspecting that it's because lambda's operator() is constexpr by default from C++17 on, but I didn't find details in cppreference about constexpr function about the usability of its argument in constant expressions.
But then why does it not work with the size_capt version?
[EDIT] I updated the example above with a constexpr function instead of a lambda and showing the difference between a direct usage and an usage through a function call. I hope it helps clarifying the issue and can be used to improve the (already interesting) provided answers.
Is it the callable copy (when passed as argument) that is breaking the requirements for constant expression?
Answer to the original question.
The reason why that code works is in the comments, but it maybe useful to share the thought process one could follow to get to the answer:
std::array<int, callable()>compiles,callable()must be known at compile time,callableis aconstexprcallable (e.g. aconstepxrfunction or theconstexproperator()of a lambda);callable, the question becomes: why isoperator()constexprfor[]() { return std::size_t{42}; }but not for[argc]() { return std::size_t(argc); }? And the answer is the other answer :DAnswer to the updated question
I think it all boils down to a much simpler example: why does the following fail to compile?
where
0is definitely a constant expression, just likesize(), but with less room for doubts.The point is just that function parameters are not
constexpr, in the sense that across function boundaries they loseconstexprness, and so even the state of the lambda, which is part of the object, not part of its type (just like the0inFoo{}givenstruct Foo { int i{0}; };, but unlike, say, the size of astd::array, which is embedded in the type itself), can't be used in a constant expression.One more detail about the sub-question...
... as to why
make_array_ok(foo<42>)fails to compile, it's useful to look at it from this perspective: The type offoo<42>isstd::size_t(). Yes, it isconstexpr, but it will causemake_array_okto be instantiated withCallablebeingstd::size_t(*)()((*)is because of function to pointer-to-function decay).Essentially, passing
foo<42>tomake_array_okhas caused the following instantiation:Now, the question to ask oneself is: what would the instatiation be, if I called
make_array_ok(foo<43>)?Well, that's easy, because
foo<43>has the same type asfoo<42>: it's the same instatiation! And you can't possibly have 1 instatiation to return different values depending what the value ofcallableis.This shows that unless the value returned by the argument to
make_array_okis part of the type of that argument, that value will not be available inmake_array_ok's body at compile-time, but only at run-time.When the value returned by the argument passed through
callableis part ofcallable's type, as it happens when you pass a stateless lambda returning a literal, then yeah, that value is available inmake_array_ok's body because it can be retrieved from the type bound to that specific instantiation ofmake_array_ok.