Как мы можем применить функцию без vararg к va_list?

Предыстория

Я портирую платформу модульного тестирования QuickCheck на C (см. Рабочий код на странице GitHub). Синтаксис будет таким:

for_all(property, gen1, gen2, gen3 ...);

Где property - функция для тестирования, например bool is_odd(int). gen1, gen2 и т. Д. - это функции, которые генерируют входные значения для property. Некоторые генерируют целые числа, некоторые генерируют символы, некоторые генерируют строки и так далее.

for_all примет функцию с произвольными входами (любое количество аргументов, любые типы аргументов). for_all запустит генераторы, создав тестовые значения для передачи функции свойств. Например, свойство is_odd - это функция с типом bool f(int). for_all будет использовать генерацию для создания 100 тестовых случаев. Если свойство возвращает false для любого из них, for_all распечатает неверные значения тестового примера. В противном случае for_all напечатает "SUCCESS".

Таким образом, for_all следует использовать va_list для доступа к генераторам. Как только мы вызываем функции генератора, как передать их функции свойств?

Пример

Если is_odd имеет тип bool f(int), как бы мы реализовали функцию apply() с таким синтаксисом:

apply(is_odd, generated_values);

Вторичный выпуск

См. SO.

Как мы можем грамотно распечатать произвольные значения неудачного тестового примера? Тестовый пример может быть одним целым числом или двумя символами, или строкой, или некоторой комбинацией вышеперечисленного? Мы не будем знать заранее, использовать ли:

  • printf("%d %d %d\n", some_int, some_int, some_int);
  • printf("%c\n" a_character);
  • printf("%s%s\n", a_string, a_struct_requiring_its_own_printf_function);

person mcandre    schedule 23.09.2011    source источник
comment
Я не думаю, что следую - вы должны a) вызывать каждый генератор один раз и сразу же получать всю коллекцию сгенерированных значений или b) вызывать каждую функцию генератора в цикле для получения последующих значений для проверки?   -  person julx    schedule 23.09.2011
comment
Хорошо, я прочитал исходный код, поэтому я понимаю, что на самом деле c) каждый генератор предоставляет последующие значения для определенного аргумента функции.   -  person julx    schedule 23.09.2011
comment
Во-первых: запустите указанные генераторы, чтобы создать тестовый пример. Пример: testme(int, char, char) требуется случайное целое число и два случайных символа. Как только мы выясним, как это сделать, у нас будет for_all запускать не один, а 100 тестовых примеров в большом цикле.   -  person mcandre    schedule 23.09.2011
comment
Для вашей вторичной проблемы вам нужно будет создать аргументы и выложить их либо в специальной функции ведения журнала, которую вы также передаете, либо путем запуска va_list и последующей передачи списка start в функция печати или обработки какого-либо вида (вы можете использовать тип va_list в качестве параметра функции, позволяя вам обернуть или иным образом возиться с функциями vararg).   -  person ssube    schedule 23.09.2011
comment
@peachykeen Да, я полагаю, мне придется изменить API на for_all(property, gen1, print1, gen2, print2, ...); для обработки произвольно сложных типов данных (подумайте о деревьях RedBlack).   -  person mcandre    schedule 23.09.2011


Ответы (1)


Язык C - это язык со статической типизацией. У него нет возможностей отражения времени выполнения, как в других языках. Он также не предоставляет способов создания произвольных вызовов функций из типов, предоставляемых средой выполнения. У вас должен быть способ узнать, что такое сигнатура функции is_odd, сколько параметров она принимает и каковы типы этих параметров. Он даже не знает, когда достиг конца списка аргументов ...; вам нужен явный терминатор.

enum function_signature {
    returns_bool_accepts_int,
    returns_bool_accepts_float,
    returns_bool_accepts_int_int,
};

typedef bool (*function_returning_bool_accepting_int)(int);
typedef int (*function_generates_int)();

void for_all(function_signature signature, ...)
{
    va_list ap;
    va_start(ap, signature);
    switch (function_signature)
    {
    case returns_bool_accepts_int:
        {
            function_returning_bool_accepting_int fn = va_arg(ap, function_returning_bool_accepting_int);
            function_generates_int generator;
            do {
                generator = va_arg(ap, function_generates_int);
                if (generator) fn(generator());
            } while (generator);
        }
        break;
    ... etc ...
    }
}

Ваша проблема в том, что QuickCheck был разработан для использования преимуществ высокой динамической программируемости JavaScripts, чего не хватает в C.

Обновление. Если вы разрешаете произвольные сигнатуры функций, вам нужен способ снова сделать их статическими, например, заставив вызывающий объект предоставить соответствующие адаптеры.

typedef void (*function_pointer)();
typedef bool (*function_applicator)(function_pointer, function_pointer);

void for_all(function_applicator apply, ...)
{
    va_list ap;
    va_start(ap, apply);
    function_pointer target = va_arg(ap, function_pointer);
    function_pointer generator;
    do {
        generator = va_arg(ap, function_pointer);
        if (generator) apply(target, generator);
    } while (generator);
}

// sample caller
typedef bool (*function_returning_bool_accepting_int)(int);
typedef int (*function_returning_int)();
bool apply_one_int(function_pointer target_, function_pointer generator_)
{
    function_returning_bool_accepting_int target = (function_returning_bool_accepting_int)target_;
    function_returning_int generator = (function_returning_int)generator_;
    return target(generator());
}

for_all(apply_one_int, is_odd, generated_values1, generated_values2, (function_pointer)0);



}
person Raymond Chen    schedule 23.09.2011
comment
Да, C не может динамически определять сигнатуру типа функции. Вот почему необходимо предоставить явные функции генератора. И мы не можем жестко запрограммировать это enum, поскольку пользователь может захотеть протестировать функцию над пользовательскими типами (структурами, объединениями и т. Д.). - person mcandre; 23.09.2011
comment
Затем вам нужно нажать на набор текста в вызывающем абоненте. Смотрите обновление. (Заполнение пробелов я оставляю в качестве упражнения. Я не могу сделать все за вас ...) - person Raymond Chen; 23.09.2011
comment
Хех, нет, я бы не ожидал, что ты этого увидишь. :) В вашем коде похоже, что is_odd вызывается отдельно для generate_values1 и generated_values2. Помните, что каждый генератор представляет собой часть ввода свойства. is_odd(int) имеет арность 1, поэтому у него должен быть один генератор (gen_int). is_equal_to(int a, int b) имеет арность 2, поэтому у него будет два генератора (gen_int, gen_int). - person mcandre; 23.09.2011
comment
Вы можете расширить дизайн для поддержки функций с арностью больше 1. - person Raymond Chen; 23.09.2011
comment
Дизайн требует создания вспомогательных функций для каждой возможной сигнатуры функции. Я оставлю перечисление бесконечных комбинаций в качестве упражнения для кого-то другого. - person mcandre; 24.09.2011
comment
Ответственность за предоставление адаптера ложится на вызывающего абонента. - person Raymond Chen; 24.09.2011