Skip to content

Compile-time environment variable #628

@abstract-meta-magic

Description

@abstract-meta-magic

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

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

Ссылки

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions