6

I am trying to use the return value from a consteval function UniqueSize() inside another consteval function UniqueArra to declare an array, but this results in a compiler error saying that UniqueSize() is not a constant expression.

However, if I use UniqueSize() inside main() and use its return value to declare an array the compiler just accepts it. Why is this happening? Can someone please explain the difference of constant expressions in these contexts?

See the code below. https://godbolt.org/z/nd18WE8M5

template <typename... Args>
consteval auto UniqueSize(Args&&... args)
{
    std::vector v{std::forward<Args>(args)...};
    std::sort(v.begin(), v.end());
    auto newEnd = std::unique(v.begin(), v.end());
    return std::distance(v.begin(), newEnd);
}

template <typename... Args>
consteval auto UniqueArray(Args&&... args)
{
    // Calling UniqueSize to determine the size of the array returned
    constexpr auto sz = UniqueSize(std::forward<Args>(args)...);
    std::array<int, sz> arr{};

    std::vector v{std::forward<Args>(args)...};
    std::sort(v.begin(), v.end());
    auto newEnd = std::unique(v.begin(), v.end());
    std::copy(v.begin(), v.end(), arr.begin());
    return arr;
}

int main()
{
    // This compiles fine
    constexpr auto sz = UniqueSize(2, 3, 4, 4, 5, 5);
    static_assert(sz == 4);
    std::array<int, sz> array{};

    // This gives an error complaining that `sz` inside UniqueArray is not a constant expression. 
    // auto arr = UniqueArray(2, 3, 4, 4, 5, 5);
}
3
  • 1
    Parameters are not constant expression, even in consteval/constexpr functions. Commented Jun 16 at 14:08
  • 1
    template <auto... Args> consteval auto UniqueArray() would be possible. Commented Jun 16 at 14:11
  • There is a proposal (p3491r1) which might help for future. Commented Jun 16 at 14:52

2 Answers 2

9

As explained in this SO post, parameters of consteval function are not considered consteval.

To allow what you are aiming for, you need to move the parameters to be template parameters. Something like this should work:

template <auto... args>
consteval auto UniqueBigArray() {
    using TYPE = std::common_type_t<decltype(args)...>;
    std::array<TYPE, sizeof...(args)> arr{args...};
    std::sort(arr.begin(), arr.end());
    auto last = std::unique(arr.begin(), arr.end());
    return std::pair {arr, std::distance(arr.begin(), last)};
}

template <auto... args>
consteval auto UniqueSize() {
    return UniqueBigArray<args...>().second;
}

template <auto... args>
consteval auto UniqueArray() {
    constexpr auto p = UniqueBigArray<args...>();
    std::array<typename decltype(p.first)::value_type, p.second> arr{};
    std::copy(p.first.begin(), p.first.begin() + p.second, arr.begin());
    return arr;
}

int main() {
    constexpr auto sz = UniqueSize<2, 3, 4, 4, 5, 5>();
    static_assert(sz == 4);

    constexpr auto arr = UniqueArray<2, 3, 4, 4, 5, 5>();
    static_assert(arr == std::array{2, 3, 4, 5});

    constexpr auto arr2 = UniqueArray<3, 4.0, 4, 5, 5.5>();
    static_assert(arr2 == std::array{3.0, 4.0, 5.0, 5.5});
}

Code: https://godbolt.org/z/Gvh1xaY67

Sign up to request clarification or add additional context in comments.

Comments

1

As a complement to @Amir Kirsh's answer, it is possible to generalize a little bit with arrays of a type that is not default constructible such as

struct foo {
    int n;
    constexpr foo (int n) : n(n) {}   // custom constructor => no more default constructor
    constexpr bool operator< (foo other) const { return n<other.n;  }
    constexpr bool operator==(foo other) const { return n==other.n; }
};

One can slightly modify the UniqueArray in order to initialize directly the result array with the help of a template lambda function

template <auto... args>
consteval auto UniqueArray() {
    constexpr auto p = UniqueBigArray<args...>();
    return  [&] <std::size_t...Is>  (std::index_sequence<Is...>)  {
        return std::array<typename decltype(p.first)::value_type, p.second> {{ p.first[Is]... }};
    } (std::make_index_sequence<p.second>());
}

so it becomes possible to write this

constexpr auto output = UniqueArray<foo{2}, foo{3}, foo{4}, foo{4}, foo{5}, foo{5}> ();

static_assert (output == std::array<foo,4> {foo{2},foo{3},foo{4},foo{5}});

Demo

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.