Amos
Amos

Reputation: 3276

Why if constexpr fails to bypass constexpr evaluation?

I'm building a static loop for type dispatching using macros. Here is what I achieved so far.

#define LOOP(n, f)                                            \
    static_assert(n <= 8 && "static loop size should <= 8");  \
    do {                                                      \
        if constexpr (n >= 8)                                 \
            f(std::integral_constant<size_t, n - 8>());       \
        if constexpr (n >= 7)                                 \
            f(std::integral_constant<size_t, n - 7>());       \
        if constexpr (n >= 6)                                 \
            f(std::integral_constant<size_t, n - 6>());       \
        if constexpr (n >= 5)                                 \
            f(std::integral_constant<size_t, n - 5>());       \
        if constexpr (n >= 4)                                 \
            f(std::integral_constant<size_t, n - 4>());       \
        if constexpr (n >= 3)                                 \
            f(std::integral_constant<size_t, n - 3>());       \
        if constexpr (n >= 2)                                 \
            f(std::integral_constant<size_t, n - 2>());       \
        if constexpr (n >= 1)                                 \
            f(std::integral_constant<size_t, n - 1>());       \
    } while (0);

template <typename T> constexpr size_t tupleSize(T&) { return tuple_size_v<T>; }

int main() {
    auto t = std::make_tuple(1, "string", 0.2, 3, 1, 1, 1);
    LOOP(tupleSize(t), [&](auto i) { cout << std::get<i>(t) << endl; });
    return 0;
}

And the godbolt link https://godbolt.org/z/GcMZI3

The question is, why do the first four branches fail the compilation?

Upvotes: 2

Views: 117

Answers (1)

Vittorio Romeo
Vittorio Romeo

Reputation: 93274

Do not use a macro, use a function template instead. if constexpr works by discarding the non-taken branch depending on the current instantiation of a template.

template <std::size_t n, typename F>
void loop(F&& f)
{
    static_assert(n <= 8 && "static loop size should <= 8");
    if constexpr (n >= 8)
        f(std::integral_constant<size_t, n - 8>());
    if constexpr (n >= 7)
        f(std::integral_constant<size_t, n - 7>());
    if constexpr (n >= 6)
        f(std::integral_constant<size_t, n - 6>());
    if constexpr (n >= 5)
        f(std::integral_constant<size_t, n - 5>());
    if constexpr (n >= 4)
        f(std::integral_constant<size_t, n - 4>());
    if constexpr (n >= 3)
        f(std::integral_constant<size_t, n - 3>());
    if constexpr (n >= 2)
        f(std::integral_constant<size_t, n - 2>());
    if constexpr (n >= 1)
        f(std::integral_constant<size_t, n - 1>());
}

Usage:

int main() {
    constexpr auto t = std::make_tuple(1, "string", 0.2, 3);
    loop<tupleSize(t)>([&](auto i) { cout << std::get<i>(t) << endl; });
    return 0;
}

live example on godbolt.org


From cppreference:

If a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated.

Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive

Upvotes: 4

Related Questions