Rlyeh
Rlyeh

Reputation: 202

Force expression to constexpr

Given the two constexpr functions, is it possible to combine them into one function?

template <char... C>
constexpr int boo()
{
    char ch[] = { C... };
    int count = 0;

    for (char c : ch)
    {
        if (c != '0') count += 1;
    }

    return count;
}

template <char... C>
constexpr auto foo()
{
    std::array<char, boo<C...>()> x{};

    return x;
}

As the example shows I can return 'count' as a constant. My problem is I can't use 'count' as a constant in the function it's declared. That is if the body of 'boo()' is placed in 'foo()', the compiler throws up with 'count' not being a constant.

Upvotes: 2

Views: 229

Answers (2)

Acorn
Acorn

Reputation: 26066

For C++14 and up, if your goal is to "merge" the bodies, you could simply define a type inside your function template:

template <char... C>
constexpr auto foo()
{
    struct {
        constexpr int operator()() {
            char ch[] = { C... };
            int count = 0;

            for (char c : ch)
            {
                if (c != '0') count += 1;
            }

            return count;
        };
    } boo;

    std::array<char, boo()> x{};

    return x;
}

If you have C++17, you can also use lambdas in constant expressions, so you can shorten boo to:

constexpr auto boo = []() { /* ... */ };

In C++20, you will be able to write lambda expressions directly as a template argument, so you could reduce further to (if you really wanted it):

std::array<char, []() { /* ... */ }()> x{};

Having said that, in general, I would say that the usual (and cleaner) approach to have all kinds of extra code used by templates in a header but that are not part of the public interface is putting them in a detail or similarly named namespace:

namespace detail {
    template <char... C>
    constexpr int boo()
    {
        /* ... */
    }
}

template <char... C>
constexpr auto foo()
{
    /* ... detail::boo<C...>() ... */
}

Upvotes: 4

max66
max66

Reputation: 66200

The problem is that std::array needs a constant as size value.

If you define count and modify it inside foo(), count (as seen inside the foo() function) is a variable, not a constant.

So you need to modify it in another place: in a constexpr function, so the returned value become a compile-time known constant.

If you can use C++17, so template folding (with improvement from Evg and Rakete1111; thanks), you can avoid bar() at all

template <char... C>
constexpr auto foo()
{
    std::array<char, (0u + ... + (C != '0'))> x{};

    return x;
}

but if you have only C++11, you need recursion

template <typename = void>
constexpr std::size_t bar ()
 { return 0u; }

template <char C0, char ... C>
constexpr std::size_t bar ()
 { return bar<C...>() + (C0 == '0' ? 0u : 1u); }

template <char... C>
constexpr std::array<char, bar<C...>()> foo()
 { return {}; }

Upvotes: 5

Related Questions