-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Вступление - в рамках модели c++.
Переменные времени компиляции все время преследуют нас. Сейчас в основном это магия препроцессора. Но хотелось бы не выходить из модели c++, и при этом иметь удобный,типобезопасный и диагностируемый (в исключительных случаях) доступ к переменным времени компиляции. Рефлексия (R2996R13), принятая в стандарт, продемонстрировала возможность написания consteval функций, реализация которых является обязательством компилятора, а так же их удобство и эффективность.
Прототип
namespace std::cenv {
// затычка
struct err {
enum {
UNDEFINED,
STR,
NUM,
LOGIC
} value_type;
// что содержиться. может быть пустым.
std::string_view contains;
// сообщение от компилятора.
std::string_view msg;
};
// базовые вызовы
consteval std::string_view str(std::string_view name);
consteval size_t num(std::string_view name);
consteval bool logic(std::string_view name);
// вызовы с оброботкой через std::expected
consteval std::expected<std::string_view,err> str_exp(std::string_view name);
consteval std::expected<size_t,err> num_exp(std::stirng_view);
consteval std::expected<bool,err> logic_exp(std::string_view name);
// вызовы с обработкой через альтернативные значения
consteval std::string_view str(std::string_view name,std::string_view alt);
consteval size_t num(std::string_view name ,size_t alt);
consteval bool logic(std::string_view name,bool alt);
}Исключительные ситуации и диагностика.
Диагностика касается лишь базовых вызовов std::cenv, но не вызовов с постфиксом *_exp или альтернативным значением.
| Сценарий Ошибки | Предлагаемое Сообщение |
|---|---|
| Отсутствие Значения | compile-time environment variable with name [NAME] and type [TYPE] is undefined, but used in [FILE]:: [LINE]. |
| Несовпадение Типов | compile-time environment variable with [NAME] is defined as [TYPE], but used as [REQUESTED_TYPE] in [FILE]::[LINE]. |
| Переопределение стандартизованных или уже определенных переменных |
compile-time environment variable with name [NAME] can't be redefined. |
Почему так радикально
Если используется один из базовых вызовов std::cenv, возврат значения по умолчанию или замалчивания ошибки может привести к непредсказуемым последствиям. Базовые вызовы обязывают корректно определять переменные времени компиляции. В иных случаях, используются альтернативные вызовы с обработкой.
Ограничения
- На разумное кол-во символов в STR
- NUM не может быть отрицательным значением.(std::size_t).
- NUM ограниченно максимальным значением std::size_t
Флаги для компилятора
- --env-num=[имя] [значение(конвертируемое в std::size_t)]
- --env-str=[имя] [значение(строковое)]
- --env-logic=[имя] [значение(конвертируемое в bool)]
Использование
Вместо:
#ifndef MY
#define MY 22
#endif
// ... какой-то код
std::byte buff[MY];Использование по месту с указанием альтернативы:
std::byte buff[std::cenv::num("lib::buff-size",20)];
Больше примеров:
void foo() {
// попaдет в финальный бинарник ))
auto key = std::cenv::str("lib::sicret");
auto server = std::cenv::str("lib::server");
internet::connect(server,key);
}
void bar() {
if constexpr (std::cenv::logic("lib::async")) {
async_baz();
} else if constexpr (std::cenv::logic("lib::parr-spec--custom")) {
auto data = /* ... */;
// это не часть стандарта и никогда ею не будет ((
extern void __my_custom_library_call(void*,std::int32_t)(&data,4);
// ибо объявление подобного вызова
// не может являться единым источником правды.
} else {
// обычный вызов
baz();
}
}
void foo() {
constexpr auto env = std::cenv::logic_exp("lib::access")
static_assert(env,std::format("lib doc : please use flag --env-logic=lib::access with true, and ... , with value : {}",env.error().msg));
// было бы здорово иметь возможность писать так:
static_assert(auto env = std::cenv::logic_exp("lib::access"),
std::format(/* msg */,env.error().msg));
// или для точности:
static_assert(constexpr auto env = std::cenv::logic_exp("lib::access"),
std::format(/* msg */,env.error().msg));
// like if\for init:
static_assert(constexpr auto env = std::cenv::logic_exp("lib::access");
env,std::format(/* msg */,env.error().msg));
// но это не стандартизировано.
}
CMake
target_add_env(${PROJECT_NAME} my_logic LOGIC ON);
target_add_env(${PROJECT_NAME} my_str STR "Hello World");
target_add_env(${PROJECT_NAME} my_num NUM 4);
# по умолчанию всегда PRIVATE
target_add_env(${PROJECT_NAME} PUBLIC "biglib::intro" STR "Hello World");
target_add_env(${PROJECT_NAME} PRIVATE "biglib::intro" STR "Hello World");
target_add_env(${PROJECT_NAME} INTERFACE "biglib::intro" STR "Hello World");
# добовление env на проект
add_env("biglib::log" STR "Good")
Влажные мечты
Прототип
namepace std::cenv {
consteval std::span<const std::byte> file(std::string_view);
consteval std::expected<std::span<const std::byte>,std::string_view> file_exp(std::string_view);
}CMake:
# флаг компиляции --env-file=config [путь к файлу]
target_add_env(${PROJECT_NAME} config FILE "./config.json");
Вдохновение
По факту это std::embed(P1040R8), но чуть слаще.
Сахар
std::cenv::file зависит от логического имени, и в отличие от std::embed не требует чтобы файл был рядом. Из этого следует что, std::cenv исключает зависимость от файловой системы(windows".\configs\my.json",linux"./configs/my.json"). Также std::cenv::file позволяет разработчику обрабатывать исключительные ситуации, с пользовательскими сообщениями через static_assert.
consteval auto foo() {
// std::expected<std::span<const std::byte>, std::string_view>
// или же std::expected<std::file, std::string_view>
auto cfg = std::cenv::file_exp("config");
if (cfg) {
auto cfg_v = cfg.value();
} else {
static_assert(cfg,"error");
}
}
Cmake файлы шейдеров:
set(SHADERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/shaders/glsl")
set(SHADER_ENV_PREFIX "shader::glsl::")
set(SHADER_LIST_VAR_NAME "shader::glsl::list")
set(ADDED_SHADER_NAMES "")
file(GLOB_RECURSE GLSL_FILES "${SHADERS_DIR}/*.vert"
"${SHADERS_DIR}/*.frag"
"${SHADERS_DIR}/*.comp"
"${SHADERS_DIR}/*.geom")
foreach(FILE_PATH ${GLSL_FILES})
get_filename_component(FILE_NAME ${FILE_PATH} NAME)
set(ENV_NAME "${SHADER_ENV_PREFIX}${FILE_NAME}")
target_add_env(${PROJECT_NAME} PRIVATE ${ENV_NAME} FILE "${FILE_PATH}")
list(APPEND ADDED_SHADER_NAMES ${FILE_NAME})
endforeach()
if (ADDED_SHADER_NAMES)
string(JOIN "," SHADER_LIST_STR ${ADDED_SHADER_NAMES})
else()
set(SHADER_LIST_STR "")
endif()
target_add_env(${PROJECT_NAME} PUBLIC ${SHADER_LIST_VAR_NAME} STR "${SHADER_LIST_STR}")Использование std::cenv для возможности обработки шейдеров:
consteval /* impl */ get_baked_shaders(/* list of string_view */ list) {
// ...
// like std::tuple<std::string_view,...>
template for(auto&& shader_env : list) {
auto shader = std::cenv::file(shader_env);
if (shader) {
// что-то сделать
} else {
// Ошибка ?
}
}
//...
}
void foo() {
constexpr auto shader_list = make_viwe_list(std::cenv::str("shader::glsl::list"));
auto shaders = get_baked_shaders(shared_list);
// ... использование
}
Модули
В теории информацию о используемых std::cenv можно хранить в BMI, но я в этом вопросе крайне слаб и предложить решение не могу...
Однако могу предположить, что std::cenv более дружелюбное к модулями решение, чем директивы препроцессора.
Стандартизованные переменные
Причины и тп.
- std::consteval_platform -> str
- std::consteval_arch -> str
- std::target_platform -> str
- std::target_arch -> str
- std::compiler -> str
- std::compiler_version -> str ?
- std::language_standard -> num ?
- std::build_type -> str
- и так далее.
Спор о необходимости INT, LONG LONG, FLOAT, DOUBLE
Я считаю, что система переменных этапа компиляции должна быть минималистична. Добавление знаковых чисел и чисел с плавающей точкой может серьезно усложнить процессы. std::size_t == unsigned long(не во всех случаях) вполне хватает для базовых задач в компиляции.
Самое главное, значения не изменяющиеся между компиляциями лучше объявлять как constexpr, а не использовать механизмы std::cenv.
Но это лишь мои мысли, а не сообщества.