Условный constexpr на независимом условии

Предположим, у меня есть функция конфигурации, которую должен определить пользователь библиотеки, которая может быть или не быть 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>{}; }

Теперь у меня есть функция, которая ведет себя по-разному в зависимости от значения iterations(), но эта функция должна быть constexpr, если есть iterations, потому что я хочу, чтобы пользователь мог использовать ее в constexpr, если они настроили библиотеку для этого:

/*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;
}

Что я могу сделать, чтобы algorithm сделать функцию constexpr, если iterations() есть? Короче, я хочу что-то вроде constexpr(constexpr(::iterations())).


Обратите внимание, что условное constexpr обычно зависит от параметра шаблона, как в функция-член условно constexpr, и в этом случае ключевое слово constexpr может быть просто используется, но в этом случае я хочу, чтобы constexpr был обусловлен чем-то, что является не параметром шаблона, а статически известной функцией. Пометка algorithm как constexpr является ошибкой компиляции:

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

person Justin    schedule 26.02.2021    source источник
comment
См. также: stackoverflow.com/a/55290363/5684257.   -  person HTNW    schedule 26.02.2021


Ответы (3)


Вы можете отключить диагностику компилятора, обеспечив наличие некоторого набора параметров шаблона и аргументов функции, для которых вызов вашей функции является константным выражением, даже если ::iterations() не является постоянное выражение. Например:

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) является постоянным выражением всякий раз, когда есть meow, поэтому компилятор не может жаловаться (https://godbolt.org/z/GvE9ME ).

person Casey    schedule 26.02.2021
comment
Это своего рода издевательство над правилом, не так ли? Вроде как нет, ты не можешь сделать static_assert(false);, но да, ты можешь сделать static_assert(always_false_v<T>); - person Barry; 26.02.2021
comment
@ Барри Да, это так. Было бы неплохо, если бы был более явный способ отказаться от некоторых полезных проверок, которые язык предоставляет для шаблонов, но без этого у нас есть грубые обходные пути. template <bool B> struct S { template <bool BB = B, class = enable_if_t<BB>> // ... — это классический случай, который мы исправили в основном языке с помощью операторов require, но, конечно, не единственный случай. - person Casey; 01.03.2021

Вдохновившись вашим ответом, вы можете использовать концепции, чтобы навязать, что iteration() возвращает интегральную константу.

Важно помнить, что algorithm() должна быть функцией или концепцией шаблона (requires) не может работать, поэтому вы можете наложить неиспользуемый параметр шаблона по умолчанию.

//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()); }

Ниже приведен ваш упрощенный пример

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());
}
person max66    schedule 26.02.2021
comment
Я совершенно уверен, что вы можете использовать requires для не-шаблонов (конечно, вы можете для не-шаблона внутри шаблонного класса) и что GCC жалуется только на то, что реализация их концепций неполна. Несмотря на это, это отличный способ обойти проблемы компилятора, создающего тело algorithm против ограничения. - person Justin; 26.02.2021
comment
@Justin. Согласно этой странице, шаблоны классов, шаблоны функций и не- функции шаблона (обычно члены шаблонов классов) могут быть связаны с ограничением, которое определяет требования к аргументам шаблона, поэтому (если я правильно понимаю) требуются аргументы шаблона. - person max66; 26.02.2021
comment
@Justin - сюрреалистический аспект заключается в том, что это решение работает и в том случае, если параметр шаблона не задействован. - person max66; 26.02.2021

Я согласен с тем, что версия constexpr должна быть: ... std::integral_constant<int, 10>

Поскольку вы согласны с требованием, чтобы версия функции constexpr имела другой тип возвращаемого значения, мы можем обнаружить этот специальный тип и обусловить constexpr этим, используя ограничения (C++20 requires). Обратите внимание, что мы должны дополнительно обернуть тело в if constexpr, потому что компилятор по-прежнему проверяет тело функции:

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());
}

Демо

person Justin    schedule 26.02.2021
comment
Вопрос: если накладываете requires is_integral_constant<decltype(::iterations())>, то зачем проверяете if constexpr (is_integral_constant<decltype(::iterations())>) внутри тела? Никогда не бывает правдой? - person max66; 26.02.2021
comment
Я имею в виду: что не так с просто constexpr std::uint32_t algorithm(std::uint32_t x) requires is_integral_constant<decltype(::iterations())> { return ::algorithm_impl(x, ::iterations()); } ? - person max66; 26.02.2021
comment
@ max66 Да, это всегда так, но без проверки и просто с return :;algorithm_impl(x, ::iterations()) компилятор жалуется, что функция constexpr никогда не является constexpr (единственный путь вызывает неконстексную ::iterations()) - person Justin; 26.02.2021
comment
Это часть, которую я не понимаю: единственный путь вызывает не-constexpr ::iterations(). Учитывая (require), что decltype(::iterations()) является целочисленной константой, как может быть не constexpr? - person max66; 26.02.2021
comment
@max66 См. godbolt.org/z/ooY8bW . Я не знаю, что в стандарте заставляет это происходить, но это происходит (это может быть даже ошибка в реализации концепций clang) - person Justin; 26.02.2021
comment
Добавлен ответ, чтобы объяснить, в чем проблема. - person max66; 26.02.2021