Как перевести `void (* fn) (const char *, ...)` в `std :: function` и наоборот - PullRequest
5 голосов
/ 29 марта 2019
typedef void(*fn1)(const char *, ...);
typedef std::function<void(const char *, ...)> fn2; // has initializer but incomplete type

Интуитивно, это практически то же самое для меня, но, очевидно, моя интуиция подводит меня. Как бы я согласовал эти типы данных?

  1. Как fn2 является неполным типом?
  2. Какие изменения необходимы для подписи fn2, чтобы я мог назначить ей тип fn1?
  3. Когда я создаю лямбду для присваивания fn2, как мне получить доступ к списку аргументов с переменными параметрами?

    Другими словами, что лямбда-эквивалент эквивалентен следующему?

    void fn1_compatible (const char * format, ...) {
      va_list args;
      va_start(args, format);
      //TODO: Do stuff with variadic arguments
      va_end(args);
    }
    

ПРИМЕЧАНИЕ: Кроме того, эти подписи связаны с ведением журнала, но, пожалуйста, ответьте на вопрос в общем (не ведении журнала) контексте.

Ответы [ 4 ]

3 голосов
/ 29 марта 2019

Функции Variadic не поддерживаются std::function.std::function принимает один тип и выглядит примерно так:

template<class>
class function;  // Intentionally incomplete

template<class Ret, class... Args>
class function<Ret(Args...)> {
    // Deduce return type and argument types from function type
};

Но это не выводит типы для функции с переменным числом.Так что void(const char*) будет работать (Ret - это void, а Args... - это const char*), но void(const char*, ...) не будет работать (так как это должно быть выведено из Ret(Args..., ...))

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

decltype(auto) my_variadic_function(const char * format, ...) {
  va_list args;
  va_start(args, format);
  try {
      auto&& return_value = vmy_variadic_function(format, args);
  } catch (...) {
      va_end(args);
      throw;
  }
  va_end(args);
  return std::forward<decltype(return_value)>(return_value);
}

void vmy_variadic_function(const char* format, va_list args) {
    // Do stuff with args
}

А затем передать vmy_variadic_function в std::function<void(const char*, va_list)>.

2 голосов
/ 29 марта 2019

Насколько я знаю, вы не можете.

Если вы можете немного изменить свою предпосылку. То есть; вместо использования, например, printf, вы можете использовать vprintf. Тогда вы могли бы иметь:

using fn2 = std::function<int(const char*, va_list)>;
fn2 fun = vprintf;

Затем вы можете предоставить функцию-оболочку для вызова fn2 с ... аргументами:

int fun_wrapper(const char *format, ...) {
    va_list args;
    va_start(args, format);
    int ret = fun(format, args);
    va_end(args);
    return ret;
}

Обычно ... функции - это просто оболочки для альтернативы va_list, которая содержит фактическую реализацию. Так же, как показано, fun_wrapper является оберткой для fun.

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

1 голос
/ 29 марта 2019

Как уже отмечали другие, нет способа отобразить список переменных в стиле 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, с другой стороны, можно безопасно копировать бит за битом и считать логически идентичным.

0 голосов
/ 29 марта 2019

Лямбда-форма для varargs представляет собой простое преобразование автономной функциональной формы.

auto thing = [](const char * format, ...) {
  va_list args;
  va_start(args, format);
  //TODO: Do stuff with variadic arguments
  va_end(args);
};

Это принято G ++.

К сожалению, в clang, похоже, есть ошибка, из-за которой он не распознает, что его ключевое слово __builtin__vastart используется внутри лямбды со списком переменных аргументов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...