Позволит ли consteval использовать static_assert для аргументов функции?

В настоящее время вы не можете использовать static_assert проверить параметры constexpr функция, даже если все вызовы к ней действительно constexpr, Это имеет смысл, потому что компилятор все еще должен создать экземпляр этой функции без constexpr в случае, если какой-то другой модуль попытается вызвать его. К сожалению, это так, даже если функция static или в анонимном пространстве имен.

C++ 20, однако, введет новое ключевое слово consteval который как constexpr но это не позволяет вызывать функцию не-constexpr способом. В этом случае компилятор может точно знать, что параметры функции всегда будут известны во время компиляции. Следовательно, в теории должно быть возможно проверить их static_assert,

Вопрос в том, позволяет ли это стандарт?


Пример:

#include <iostream>

consteval char operator""_bchar(const char text[], const size_t length)
{
    static_assert(length == 8, "Binary char has to have 8 digits!"); // <-- This is currently not possible.
    uint8_t byte = 0;
    for (size_t i = 0; i != length; ++i)
    {
        byte <<= 1;
        byte |= text[i] == '1' ? 0b00000001 : 0b00000000;
    }
    return byte;
}

int main()
{
    std::cout << "01000001"_bchar << std::endl;
    return 0;
}

Я спрашиваю, потому что я собираюсь написать некоторые пользовательские литералы (более сложные, чем в примере). У меня есть возможность использовать расширения компилятора, чтобы справиться с проверкой или немного подождать обновления компилятора и написать полностью совместимый со стандартом код.

3 ответа

Позволит ли consteval использовать static_assert для аргументов функции?

Нет. Аргументы функций никогда не были и не будут использоваться в качестве константных выражений.

Есть разница между чем-то, что постоянно оценивается и может использоваться как константное выражение. consteval гарантирует, что мы находимся в постоянном контексте оценки, но это также не заставляет все становиться постоянными выражениями.

Чтобы аргументы функции можно было использовать в качестве константных выражений, вам нужно сделать все неявным образом шаблоном:

template <int> struct X { };

consteval auto foo(int i) {
    static_assert(i > 10); // in order to allow this...
    return X<i>{};         // ... you'd have to allow this too
}

И сейчас foo(20) а также foo(30) вернуть разные типы. Это шаблон.


Важную справочную информацию для понимания того, почему это фундаментальное и неотъемлемое ограничение, можно найти в " Переводе и оценке Эндрю Саттона : ментальная модель для метапрограммирования во время компиляции":

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


С участием static_assert в частности, вы можете добавить обходной путь только для того, чтобы вызвать сбой компиляции. Это просто добавление всего, что не может быть использовано во время постоянной оценки. Подобно:

#define CONSTEVAL_STATIC_ASSERT(c, msg) do { if (!(c)) throw msg; } while(false)

как в:

consteval char operator""_bchar(const char text[], const size_t length)
{
    CONSTEVAL_STATIC_ASSERT(length == 8, "Binary char has to have 8 digits!");
    // ...
}

Я согласен с комментаторами выше - это невозможно использоватьstatic_assert()с аргументами функции, но все еще возможно вызвать ошибку компиляции в функции consteval при условии аргумента. Т.е. получить тот же эффектstatic_assertпредназначен для.

      consteval char operator""_bchar(const char text[], size_t length)
{
    //static_assert(length == 8, "Binary char has to have 8 digits!");
    length /= (length == 8); // Binary char has to have 8 digits!
}

Хитрость в том,(length != 8)запускает деление на ноль, которое не является постоянным выражением.

Ошибка компиляции будет выглядеть так (gcc-11):

      test.cpp: In function ‘int main()’:
test.cpp:110:18:   in ‘constexpr’ expansion of ‘operator""_bchar(((const char*)"12345"), 5)’
test.cpp:104:12: error: ‘(5 / 0)’ is not a constant expression
   104 |     length /= (length == 8); // Binary char has to have 8 digits!
       |     ~~~~~~~^~~~~~~~~~~~~~~~

!!!ВНИМАНИЕ,ВНИМАНИЕ,ВНИМАНИЕ!!!: это работает вconstevalТОЛЬКО функции . Если используется вconstexprфункции, ваша программа будет убита с ошибкой деления на ноль . Использоватьassert()вместо этого или выдать исключение.

Я изменил технику из ответа @Barry, чтобы она работала во всех случаях.consteval,constexprи обычные функции.

      #include <assert.h>
#include <type_traits>

#define constexpr_assert(expression) do{if(std::is_constant_evaluated()){if(!(expression))throw 1;}else{assert(!!(expression));}}while(0)

Когда поддержка C++23 станет лучше, можно будет использоватьif constevalчтобы немного улучшить его:

      #include <assert.h>

#define constexpr_assert(expression) do{if consteval{if(!(expression))throw 1;}else{assert(!!(expression));}}while(0)
Другие вопросы по тегам