Можете ли вы передать переменное число аргументов одного типа в функцию без использования массивов? - PullRequest
0 голосов
/ 21 мая 2018

Мне нужно перейти от минимума до одного к максимум семи путям к файлу для функции.Существует соглашение, что одного пути к файлу достаточно, чтобы определить, как обрабатывать каждый файл.

Порядок параметров не имеет значения.

Очевидный вариант для обработки этого (тот, который я в настоящее времяреализовано) - передать пустую строку в качестве параметра для неиспользуемых слотов.

Еще один способ - передать параметры в виде массива или вектора.

Еще один вариант - реализовать все возможные перестановки.параметров (возможно, не практично).

Интересно, есть ли способ просто указать, что число параметров может изменяться, а затем просто передать сами параметры.

Так, например,при условии, что существует только одна реализация f () со специальным синтаксисом для обозначения различного количества параметров

Все следующие компоненты должны компилироваться:

int main()
{
   f(file);
   f(file1, file2);
   f(file1, file3, file2, file6);
}

Есть ли способ добиться этого вC ++?

Ответы [ 3 ]

0 голосов
/ 21 мая 2018

Если вам действительно нужно переменное (неограниченное) количество аргументов :

В противном случае, если у вас естьa фиксированное количество (необязательных) параметров :

  • Если вы используете C ++ 20 или более позднюю версию :

  • Если вы используете C ++ 03 или более позднюю версию :

    • Используйте необязательный / необязательный тип (например, необработанный указатель, boost::optional, C ++ 17 std::optional...) - см. ответ @NicolBolas.

    • Определить все требуемые / логические перегрузки (возможно, с использованием пользовательских типов) - безобразно, но это может быть автоматизировано с помощью внешнего генератора кода и / или с помощью препроцессора.

В противном случае, если вы можете использовать другой дизайн , чтобы выполнить то же самое, вы можете выполнить любое из следующих действий - для C ++ 03 и более поздних версий.:

  • Передать указатель на struct в соответствии с предложением @ PaulMcKenzie.

  • Разработать класс, который позволяет устанавливать свойства(через конструктор и / или методы), а затем имеет функции-члены для выполнения операций с этими данными, например:

    ShaderCompiler sc(vs, fs, ...);
    sc.setGeometryShader(...);
    sc.compile();
    
  • Особенно хороший способ (см., например, QString) предназначен для разработки класса, который позволяет выполнять:

    result = ShaderCompiler()
        .vertex(...)
        .fragment(...)
        ...
        .compile()
    ;
    
  • Аналогично, использование зависимого от аргумента поиска :

    Shader()
        << Vertex(...)
        << Fragment(...)
        ...
    ;
    
0 голосов
/ 21 мая 2018

Поскольку у вас есть ограниченный набор возможностей, вот очевидный способ справиться с этим:

using opt_path = std::optional<path>;

shader compile_shaders(opt_path vs, opt_path tcs = std::nullopt, opt_path tes = std::nullopt, opt_path gs = std::nullopt, opt_path fs = std::nullopt, opt_path cs = std::nullopt)
{
  ...
}

Они просто используют аргументы по умолчанию для всех других путей шейдеров.Вы можете сказать, что предоставляется, а что нет через интерфейс std::optional.Если вы не используете C ++ 17, вы, очевидно, замените его на boost::optional или аналогичный тип.

Конечно, как бы вы ни справились с этим, это приведет к явно плохому интерфейсу.Подумайте, что нужно сделать, чтобы создать наиболее распространенный случай: вершинный шейдер в сочетании с фрагментным шейдером:

compile_shaders(vs_path, std::nullopt, std::nullopt, std::nullopt, fs_path);

Помнит ли пользователь, что между ними есть 3 этапа?Шансы хорошие, они не будут.Люди будут постоянно совершать ошибку, используя только 2 std::nullopt с или 4. И учитывая, что VS + FS является наиболее распространенным случаем, у вас есть интерфейс, в котором наиболее распространенный случай очень легко получить.неверно.

Теперь вы можете изменить порядок параметров, сделав FS вторым параметром.Но если вы хотите использовать другие этапы, теперь вам нужно найти определение функции, чтобы запомнить, какие значения соответствуют каким этапам.По крайней мере, то, как я это сделал здесь, следует конвейеру OpenGL.Произвольное отображение требует просмотра документов.

И если вы хотите создать вычислительный шейдер, вы должны помнить, что есть 6 этапов, которые вы должны явно обнулить:

compile_shaders(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, cs_path);

Сравнитьвсе это для более информативного интерфейса:

shader_paths paths(vs_path, shader_paths::vs);
paths.fragment(fs_path);
auto shader = compile_shaders(paths);

Здесь нет никакой двусмысленности.Путь к конструктору явно указывается как вершинный шейдер с использованием второго аргумента.Поэтому, если вам нужен вычислительный шейдер, вы должны использовать shader_paths::cs, чтобы выразить это.Затем пути получают фрагментный шейдер с использованием функции с соответствующим именем.После этого вы компилируете шейдеры, и все готово.

0 голосов
/ 21 мая 2018

Вы можете использовать рекурсивную функцию шаблона.

#include <iostream>

template <typename First>
void f(First&& first) {
    std::cout << first << std::endl;
}

template <typename First, typename... Rest>
void f(First&& first, Rest&&... rest) {
    f(std::forward<First>(first));
    f(std::forward<Rest>(rest)...);
}

int main() {
    f(6,7,8,9,10);
}
...