C++ "forgetting" that variable is constexpr when used as function argument

I have the following code where I am irritated by the fact that compiler is unable to see that variable passed as argument to a function is constexpr so I must use arity 0 function instead of 1 argument function.

I know this is not a compiler bug, but I wonder if there are idioms that enable to workaround this problem.

#include <array>
#include <iostream>

static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};

template <typename C, typename P, typename Y>
static constexpr void copy_if(const C& rng, P p, Y yi3ld) {
    for (const auto& elem: rng) {
        if (p(elem)){
            yi3ld(elem);
        }
    }
}

// template<std::size_t N>
static constexpr auto get_evens(/* const std::array<int, N>& arr */) {
    constexpr auto is_even = [](const int i) constexpr {return i % 2 == 0;};
    constexpr int cnt = [/* &arr, */&is_even]() constexpr {
        int cnt = 0;
        auto increment = [&cnt] (const auto&){cnt++;};
        copy_if(arr, is_even, increment);
        return cnt;
    }();
    std::array<int, cnt> result{};
    int idx = 0;
    copy_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
    return result;
}

int main() {
    // constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
    for (const int i:get_evens(/* arr */)) {
        std::cout << i << " " << std::endl;
    }
}

If it is not obvious what I want: I would like to change get_evens signature so that it is template templated on array size N and that it takes 1 argument of type const std::array<int, N>&.

The error message when I change arr to be an function argument isn't helpful:

prog.cc:25:21: note: initializer of 'cnt' is not a constant expression prog.cc:19:19: note: declared here constexpr int cnt = [&arr, &is_even]()constexpr {

#include <array>
#include <iostream>

static constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};

template <typename C, typename P, typename T>
static constexpr void invoke_if(const C& rng, P p, T target) {
    for (const auto& elem: rng) {
        if (p(elem)){
            target(elem);
        }
    }
}

constexpr bool is_even(int i) {
    return i % 2 == 0;
}

template<std::size_t N>
constexpr std::size_t count_evens(const std::array<int, N>& arr)
{
    std::size_t cnt = 0;
    invoke_if(arr, is_even, [&cnt](auto&&){++cnt;});
    return cnt;
}

template<std::size_t cnt, std::size_t N>
static constexpr auto get_evens(const std::array<int, N>& arr) {
    std::array<int, cnt> result{};
    int idx = 0;
    invoke_if(arr, is_even, [&result, &idx](const auto& val){ result[idx++] = val;});
    return result;
}

int main() {
    // constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
    for (const int i:get_evens<count_evens(arr)>(arr)) {
        std::cout << i << " " << std::endl;
    }
}

this works in g++, but in clang we get a problem because the begin on an array isn't properly constexpr with at least one library. Or maybe g++ violates the standard and clang does not.

C (programming language), C programming is a general-purpose, procedural, imperative computer programming language developed in 1972 by Dennis M. Ritchie at the Bell Telephone  C programming is a general-purpose, procedural, imperative computer programming language developed in 1972 by Dennis M. Ritchie at the Bell Telephone Laboratories to develop the UNIX operating system. C is the most widely used computer language. It keeps fluctuating at number one scale of popularity along with Java programming language, which

Learn C Programming, learn-c.org is a free interactive C tutorial for people who want to learn C, fast. The C programming language is a computer programming language that was developed to do system programming for the operating system UNIX and is an imperative programming language. C was developed in the early 1970s by Ken Thompson and Dennis Ritchie at Bell Labs.

The other answer has a correct work around but I think the reasoning has nothing to do with parameters but instead to do with the lambda capture here:

constexpr int cnt = [/* &arr, */&is_even]() 

Indeed we can test the various scenarios with this code:

#include <array> 
#include <iostream>

template <size_t N>
constexpr int foo(const std::array<int, N>& arr) {
    return [&arr] () { return arr.size(); }();
}

template <size_t N>
constexpr int bar(const std::array<int, N>& arr) {
    int res{};
    for (auto i : arr) {
        res++;
    }
    return res;
}

template <size_t N>
constexpr int baz(const std::array<int, N>& arr)     {
    constexpr int test = [&arr] () constexpr {
        return bar(arr);
    }();
    return test;
}

int main() {
    constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};
    constexpr std::array<int, foo(arr)> test{};
    constexpr std::array<int, bar(arr)> test2{};
    constexpr std::array<int, baz(arr)> test3{};
}   

Note that the line where test3 is initialized fails to compile. This, however, compiles just fine:

template <size_t N>
constexpr int baz(const std::array<int, N>& arr) {
    return bar(arr);
}

So, what's the problem here? Well lambdas are really just glorified functors, and internally it'll look something like this:

struct constexpr_functor {
    const std::array<int, 5>& arr;
    constexpr constexpr_functor(const std::array<int, 5>& test)
        : arr(test) { }
    constexpr int operator()() const {
        return bar(arr);
    }
};
// ...
constexpr constexpr_functor t{arr};
constexpr std::array<int, t()> test3{};

Notice now that we get an error message showing the real problem:

test.cpp:36:33: note: reference to 'arr' is not a constant expression
test.cpp:33:34: note: declared here
    constexpr std::array<int, 5> arr{11, 22, 33, 44, 55};

The other answer quotes Scotts Meyer's book but misinterprets the quotes. The book actually shows several examples of parameters being used in constexpr situations, but the quotes are simply saying that if you pass a non-constexpr parameter, the function can run at compile-time.

C Programming Tutorial for Beginners, The best site for C and C++ programming. Popular, beginner-friendly C and C++ tutorials to help you become an expert! Discover historical prices for C stock on Yahoo Finance. View daily, weekly or monthly format back to when Citigroup, Inc. stock was issued.

Following the Evg's suggestion, so passing the numbers as template parameters of a std::integer_sequence, but passing the integer sequence as argument of the get_evens() function, and not as template parameter, you can use the numbers directly inside get_evens().

I mean... you can simplify the get_evens() as follows (EDIT: further simplified following a suggestion from Evg (Thanks!))

template <typename T, T ... Ts>
constexpr auto get_evens (std::integer_sequence<T, Ts...> const &)
 {
   std::array<T, (std::size_t(!(Ts & T{1})) + ...)> result{};

   std::size_t idx = 0;

   ((void)(Ts & 1 || (result[idx++] = Ts, true)), ...);

   return result;
 } 

and you can use it this way

int main()
 {
   using arr = std::integer_sequence<int, 11, 22, 33, 44, 55>;

   for ( const int i : get_evens(arr{}) )
      std::cout << i << " " << std::endl;
 }

"C" Programming Language: Brian Kernighan, C is a computer programming language. That means that you can use C to create lists of instructions for a computer to follow. C is one of thousands of  C++ is a middle-level programming language developed by Bjarne Stroustrup starting in 1979 at Bell Labs. C++ runs on a variety of platforms, such as Windows, Mac OS, and the various versions of UNIX. This C++ tutorial adopts a simple and practical approach to describe the concepts of C++ for beginners to advanded software engineers. Why to Learn C++

template<auto t0, auto...ts>
struct ct_array:
  std::array<decltype(t0) const, 1+sizeof...(ts)>,
  std::integer_sequence<decltype(t0), t0, ts...>
{
  ct_array():std::array<decltype(t0) const, 1+sizeof...(ts)>{{t0, ts...}} {};
};

template<class target, auto X>
struct push;
template<auto X>
struct push<void, X>{using type=ct_array<X>;};
template<auto...elems, auto X>
struct push<ct_array<elems...>, X>{using type=ct_array<elems...,X>;};
template<class target, auto X>
using push_t= typename push<target, X>::type;

template<class target>
struct pop;
template<auto x>
struct pop<ct_array<x>>{using type=void;};
template<auto x0, auto...xs>
struct pop<ct_array<x0, xs...>>{using type=ct_array<xs...>;};
template<class target>
using pop_t=typename pop<target>::type;

template<class lhs, class rhs, class F, class=void>
struct transcribe;
template<class lhs, class rhs, class F>
using transcribe_t = typename transcribe<lhs, rhs, F>::type;

template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
  std::enable_if_t<F{}(l0) && sizeof...(lhs)>
>:
  transcribe<pop_t<ct_array<l0, lhs...>>, push_t<rhs, l0>, F>
{};
template<auto l0, auto...lhs, class rhs, class F>
struct transcribe<ct_array<l0, lhs...>, rhs, F,
  std::enable_if_t<!F{}(l0) && sizeof...(lhs)>
>:
  transcribe<pop_t<ct_array<l0, lhs...>>, rhs, F>
{};
template<auto lhs, class rhs, class F>
struct transcribe<ct_array<lhs>, rhs, F, void>
{
  using type=std::conditional_t< F{}(lhs), push_t<rhs, lhs>, rhs >;
};
template<class lhs, class F>
using filter_t = transcribe_t<lhs, void, F>;

// C++20
//auto is_even = [](auto i)->bool{ return !(i%2); };
struct is_even_t {
  template<class T>
  constexpr bool operator()(T i)const{ return !(i%2); }
};
constexpr is_even_t is_even{};

template<auto...is>
static constexpr auto get_evens(ct_array<is...>) {
  return filter_t< ct_array<is...>, decltype(is_even) >{};
}

Live example.

Test code:

auto arr = ct_array<11, 22, 33, 44, 55>{};
for (const int i : get_evens(arr)) {
    std::cout << i << " " << std::endl;
}

C Tutorial, C is a procedural programming language. It was initially developed by Dennis Ritchie in the year 1972. It was mainly developed as a system programming  The C standard library provides numerous built-in functions that your program can call. For example, strcat () to concatenate two strings, memcpy () to copy one memory location to another location, and many more functions. A function can also be referred as a method or a sub-routine or a procedure, etc. Defining a Function.

Learn C, C programming is considered as the base for other programming languages, that is why it is known as mother language. It can be defined by the following ways:. The C# and .NET type system. Language Integrated Query (LINQ) async and await. Object-oriented inheritance. Use LINQ to query data. Format text using string interpolation. Browse all tutorials. New features in C# 8.0. What's new in C# 8.0. Pattern matching. Nullable reference types. Annotate APIs for nullability. Consume and generate async streams.

Cprogramming.com: Learn C and C++ Programming, = Simple assignment operator. Assigns values from right side operands to left side operand C = A + B will assign the value of A + B to C += Add AND assignment operator. It adds the right operand to the left operand and assign the result to the left operand. C += A is equivalent to C = C + A

What is C?, C-SPAN.org gives you access to C-SPAN's daily coverage of Washington and more than 200,000 hours of extensively indexed and archived C-SPAN video.

Comments
  • "I know this is not a compiler bug" - I wouldn't be certain.
  • Cannot reproduce with latest gcc and clang.
  • @evg code I pasted works, if you change it that get_evens takes array as an argument then it does not work
  • Then please add the code that doesn't work. Function argument itself is never constexpr.
  • I've edited your code, please check if I interpreted your intention correctly.
  • nit: typo at the end: i assume you mean clang does NOT
  • can you explain how invoke_if goes around the limit mentioned in other answers(that arr is not constexpr inside the body of function)
  • another note: clang 7 compiles your code gcc.godbolt.org/z/r2xair
  • @nose the trick is the code must be valid in both run and compile time contexts. The trick is I split length from values.
  • upvoted.I hope somebody comes up with a nicer hack, but I doubt it... So I will probably accept your answer in the end. :)
  • @NoSenseEtAl, take a look at std::integer_sequence. Probably, you can use it here instead of std::array.