Как уже отмечали другие, нет способа отобразить список переменных в стиле C в строго типизированную функцию C ++.Хотя можно пойти и другим путем, и это можно сделать действительно безопасно.
Здесь я написал функцию forward_to_variadic_fn
, которая принимает функцию с переменным числом в стиле C и список строго типизированныхаргументы.Аргументы Variadic имеют множество ограничений относительно правильного использования, поэтому я решил реализовать некоторые проверки безопасности, которые вводят эти ограничения во время компиляции.Например, используя эту функцию переадресации, вы не можете случайно передать std::string
, когда вам нужно передать const char*
// true if T is trivially copy & move constructible and trivially destructible
template<typename T>
constexpr bool trivial_class = (std::is_trivially_copy_constructible_v<T> && std::is_trivially_move_constructible_v<T> && std::is_trivially_destructible_v<T>);
// true if T is acceptable for C-style va_args
template<typename T>
constexpr bool safe_for_va_args = (std::is_null_pointer_v<T> || std::is_pointer_v<T> || std::is_arithmetic_v<T> || std::is_member_pointer_v<T> || trivial_class<T>);
// true if all of Args.. are safe for C-style va_args
template<typename... Args>
constexpr bool all_safe_for_va_args = (true && ... && safe_for_va_args<std::decay_t<Args>>);
template<typename Ret, typename... Args>
Ret forward_to_variadic_fn(Ret(*the_fn)(const char*, ...), const char* format, Args... args){
static_assert(all_safe_for_va_args<Args...>, "The provided types are not safe for use with C-style variadic functions.");
return the_fn(format, args...);
}
int main(){
int n = forward_to_variadic_fn(std::printf, "Hello, %s!\n", "world");
std::cout << n << " characters were written.\n";
std::string mystr = "world!";
// This will compile but is BAD
// std::printf("Hello, %s!\n", mystr);
// The following line will not compile, which is a good thing!
// forward_to_variadic_fn(std::printf, "Hello, %s!\n", mystr);
// This is safe
n = forward_to_variadic_fn(std::printf, "Hello, %s!\n", mystr.c_str());
std::cout << n << " characters were written.\n";
return 0;
}
Живой пример здесь
Конечно, это не может спасти вас от использования неправильных флагов форматирования, но может спасти вас от множества других неопределенных действий.
Редактировать для объяснения: переменная шаблона помощника all_safe_for_va_args
служит для обеспечения ограничений на аргументы для функций с переменными числами, как описано в cppreference :
Когда функция с переменными значениями вызывается после lvalue-to-rvalue, array-to-pointerи преобразования функций в указатели, каждый аргумент, являющийся частью списка переменных переменных, подвергается дополнительным преобразованиям, известным как продвижения аргументов по умолчанию:
std::nullptr_t
преобразуется в void*
float
аргументы преобразуются в double
, как при повышении с плавающей запятой bool
, char
, short
, а перечисления с незаданной областью преобразуются в int
или более широкийтипы ger, как при целочисленном продвижении
Разрешены только арифметика, перечисление, указатель, указатель на член и аргументы типа класса (кроме типов классов с нетривиальным конструктором копирования, нетривиальным конструктором перемещения илинетривиальный деструктор, который условно поддерживается семантикой, определяемой реализацией)
Эти отдельные условия аккуратно фиксируются многими классами вспомогательных признаков и переменными шаблона помощника в <type_traits>
библиотека.Например, std::is_null_pointer_v<T>
- это константа времени компиляции, которая оценивается в true
тогда и только тогда, когда T
равно nullptr_t
.
Шаблон переменной safe_for_va_args<T>
проверяет этитребования исчерпывающие и, следовательно, справедливы, когда тип T
удовлетворяет вышеуказанным условиям.Чтобы создать шаблон переменной для того же условия, которое принимает любое количество типов, я использовал сложенное выражение для эффективного выполнения логического И над расширением пакета параметров, что можно увидеть в реализации all_safe_for_va_args<T>
.
Например, all_safe_for_va_args<int, nullptr, const char*>
оценивает:
(true && safe_for_va_args<int> && safe_for_va_args<nullptr> && safe_for_va_args<const char*>)
все из которых true
, поэтому все выражение истинно.
Это прекрасно сочетается сstatic_assert
, который проверяет пользовательское условие во время компиляции и может предоставить удобное сообщение об ошибке, а не загадочную цепочку сбоев подстановки шаблонов.
Как правило, типывам разрешено переходить к функции вариации C-стиля - это исключительно типы, которые можно копировать по битам и не требуют специального обслуживания.std::string
терпит неудачу, потому что он должен выполнять выделение памяти и освобождение, и поэтому не имеет ни тривиального конструктора, ни деструктора.const char*
или int
, с другой стороны, можно безопасно копировать бит за битом и считать логически идентичным.