Justin
Justin

Reputation: 25297

Conditional constexpr on non-dependent condition

Suppose I have a configuration function to be defined by the user of the library which may or may not be constexpr.

constexpr int iterations() { return 10; }
// Or maybe:
int iterations() { return std::atoi(std::getenv("ITERATIONS")); }

// I'm okay with requiring the constexpr version to be:
constexpr auto iterations() { return std::integral_constant<int, 10>{}; }

Now I have a function that has different behavior depending on the value of iterations(), but this function should be constexpr if iterations is, because I want the user to be able to use it in constexpr if they configured the library to allow for it:

/*constexpr*/ std::uint32_t algorithm(std::uint32_t x) {
    auto const iterations = ::iterations();

    // For illustration purposes
    for (int i = 0; i < iterations; ++i) {
        x *= x;
    }

    return x;
}

What can I do to algorithm to make the function constexpr if iterations() is? In short, I want something like constexpr(constexpr(::iterations())).


Note that "conditional constexpr" is usually dependent on a template parameter as in Conditionally constexpr member function in which case the constexpr keyword can just be used, but in this case, I want the constexpr to be conditional on something which is not a template parameter, but a statically known function. Marking algorithm as constexpr is a compilation error:

error: call to non-'constexpr' function 'bool iterations()'

Upvotes: 3

Views: 435

Answers (3)

Casey
Casey

Reputation: 42554

You can silence the compiler diagnostics by ensuring that there is some set of template parameters and function arguments for which a call to your function is a constant expression even if ::iterations() is not a constant expression. For example:

template <bool True = true>
constexpr std::uint32_t algorithm(std::uint32_t x) {
    if constexpr (True) {
        auto const iterations = ::iterations();

        for (int i = 0; i < iterations; ++i) {
            x *= x;
        }

        return x;
    } else {
        return 0;
    }
}

algorithm<false>(meow) is a constant expression whenever meow is, so a compiler cannot complain (https://godbolt.org/z/GvE9ME).

Upvotes: 4

max66
max66

Reputation: 66200

Taking inspiration from your answer, you can use concepts to impose that iteration() return a integral constant.

The important part is remember that algorithm() ha to be a template function or concept (requires) can't works, so you can impose an unused and defaulted template parameter

//VVVVVVVVVVVVVVVVVVVVVVVVVV <-- add this to activate `requires`  
  template <typename = void>
  constexpr std::uint32_t algorithm(std::uint32_t x)
          requires is_integral_constant<decltype(::iterations())>
   { return ::algorithm_impl(x, ::iterations()); }

The following is your example simplified

template <typename T>
constexpr bool is_integral_constant = false;

template <typename T, T value>
constexpr bool is_integral_constant<std::integral_constant<T, value>> = true;

constexpr std::uint32_t algorithm_impl(std::uint32_t x, int iterations) {
    for (int i = 0; i < iterations; ++i) {
        x *= x;
    }

    return x;
}

template <typename = void>
constexpr std::uint32_t algorithm(std::uint32_t x)
        requires is_integral_constant<decltype(::iterations())>
 { return ::algorithm_impl(x, ::iterations()); }

std::uint32_t algorithm(std::uint32_t x) {
    return ::algorithm_impl(x, ::iterations());
}

Upvotes: 2

Justin
Justin

Reputation: 25297

I'm okay with requiring the constexpr version to be: ... std::integral_constant<int, 10>

Because you are okay with requiring the constexpr version of the function to have a different return type, we can detect this special type and condition the constexpr on that by using constraints (C++20's requires). Note that we have to further wrap the body in if constexpr because the compiler still checks the body of the function:

template <typename T>
constexpr bool is_integral_constant = false;

template <typename T, T value>
constexpr bool is_integral_constant<std::integral_constant<T, value>> = true;

constexpr std::uint32_t algorithm_impl(std::uint32_t x, int iterations) {
    for (int i = 0; i < iterations; ++i) {
        x *= x;
    }

    return x;
}

constexpr std::uint32_t algorithm(std::uint32_t x)
        requires is_integral_constant<decltype(::iterations())> {
    if constexpr (is_integral_constant<decltype(::iterations())>) {
        return ::algorithm_impl(x, ::iterations());
    } else {
        // Unreachable, but enough to convince the compiler that there is a
        // constexpr friendly path through the function
        return 0xDEADBEEF;
    }
}

std::uint32_t algorithm(std::uint32_t x) {
    return ::algorithm_impl(x, ::iterations());
}

Demo

Upvotes: 0

Related Questions