Estou em um estágio muito inicial na escrita de um programa em C++ replicando as funções mais básicas do ls
comando bash.
Eu uso o <filesystem>
cabeçalho.
O std::filesystem::directory_iterator
e std::filesystem::recursive_directory_iterator
dessa biblioteca me ajudam a percorrer o caminho do arquivo de entrada de maneira superficial ou recursiva, respectivamente.
A entrada recebe um argv[1]
, que deve ser composto de um hífen inicial -
, seguido por letras representando sinalizadores de opção. Flag R
representa travessia recursiva de todos os subdiretórios, caso contrário, olhe apenas para o caminho fornecido.
argv[2]
é o caminho do arquivo a ser explorado.
Minha dúvida, no entanto, é sobre como executar isso de forma padronizada ou se isso é possível.
Como teste, estou usando std::variant
uma função option_R()
que seleciona o correto directory_iterator
dependendo se o -R
sinalizador está presente na entrada ou não.
Como isso seria polimorfismo de tempo de execução, estou cético quanto à possibilidade de implementar algo como o abaixo:
#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include <variant>
#include <type_traits>
#include <concepts>
using namespace std;
using namespace std::filesystem;
template<typename T> concept isIter =
is_same<T, directory_iterator>::value || is_same<T, recursive_directory_iterator>::value;
template <isIter dirIterator>
dirIterator option_R(std::string_view options, const path &my_path) {
variant<directory_iterator, recursive_directory_iterator> my_iter;
if (options.find('R') != string::npos) {
my_iter.emplace<recursive_directory_iterator>(my_path, directory_options::skip_permission_denied);
return get<recursive_directory_iterator>(my_iter);
} else {
my_iter.emplace<directory_iterator>(my_path);
return get<directory_iterator>(my_iter);
}
}
template <isIter dirIterator>
void traverse(isIter &my_iter, vector<string> &my_vec) {
cout << "This function traverses the given dir path, "
<< "stores all filenames found inside a vector." << endl;
for (const auto &entry : my_iter)
my_vec.push_back(entry.path().string());
}
int main(int argc, const char **argv) {
const path sys_path{argv[2]};
const string options{argv[1]};
vector<string> files;
if (options[0] == '-') {
if (is_directory(sys_path)) {
isIter file_iterator = option_R(options, sys_path);
traverse(file_iterator, files);
}
cout << "after this the program will parse other options" << endl;
} else {
cout << "simply print all files" << endl;
}
}
O acima não compila, entre outros motivos porque eu uso vagarosamente o conceito isIter
para invocar option_R()
em main
.
Haveria uma maneira de escrever option_R()
, de modo que ele fizesse apenas uma coisa: selecionar o correto directory_iterator
de acordo com o sinalizador -R
fornecido ou não - e passá-lo de volta para main()
?
Seria fácil agrupar option_R()
e traverse()
reunir em apenas uma função, mas isso faria várias coisas e o código seria mais difícil de entender.
Você pode conseguir o que está tentando fazer com
std::variant
estd::visit
.A
std::variant
é uma união mais segura, que você pode usar para armazenar astd::filesystem::directory_iterator
e astd::filesystem::recursive_directory_iterator
, sem precisar alocar memória para ambos os iteradores separadamente.Com
std::visit
você você pode entãostd::variant
passar seu valor para uma função modelo (ou sobrecarregada) que pode funcionar com todos os tipos que elastd::variant
possui.Usando isso, podemos fazer com que sua
option_R()
função retorne umstd::variant
dos dois iteradores mencionados acima e, então, atraverse()
função recebe umstd::variant
como argumento e o usastd::visit
para iterar sobre todos os arquivos da maneira esperada.( exemplo vivo )
Você está muito perto. O problema é que você não pode fazer sua
option_R()
função retornar um tipo de modelo de tempo de compilação que é determinado em tempo de execução. Você precisa fazer com que ela retorne astd::variant
si mesma, e então o chamador pode decidir o que fazer com o valor. Você pode usarstd::visit()
para ajudar com isso, por exemplo: