ideas icon indicating copy to clipboard operation
ideas copied to clipboard

Compile-time environment variable

Open abstract-meta-magic opened this issue 1 month ago • 0 comments

Вступление - в рамках модели 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.

Но это лишь мои мысли, а не сообщества.

Ссылки

abstract-meta-magic avatar Nov 19 '25 12:11 abstract-meta-magic