2017-10-18 6 views
0

Je configure une commande de console qui prend un nombre variable d'arguments, dont chacun peut être des types de base (int, float, bool, string) et les passe ensuite à une fonction qui a 8 surcharges pour supporter un nombre différent d'arguments de types différents. Comment est-ce que j'analyserais la chaîne de ligne de commande en valeurs basées sur leur type et les passerais ensuite à la fonction?Comment passer un nombre variable et un type d'arguments à la fonction de modèle?

Je peux récupérer chaque argument via la fonction const char* GetArg(int index). La conversion du char* au type correct n'est pas un problème, alors ne vous inquiétez pas pour cette partie. Stocker les valeurs et passer en quelque sorte à la fonction de gabarit est la partie sur laquelle je suis coincé.

Par exemple, si la commande a été exécutée avec la chaîne suivante: « commande 66 true 5 « valeur de chaîne » 0,56 »

Il serait alors divisé en les args suivants et stocké en quelque sorte:

int arg1 = GetArg(1); // 66 
bool arg2 = GetArg(2); // true 
int arg3 = GetArg(3); // 5 
char* arg4 = GetArg(4); // "string value" 
float arg5 = GetArg(5); // 0.56 

Et puis en fonction du nombre de args, appelez la fonction de modèle correct:

// The function definition looks something like this: 
void SomeFunc(); 
template<typename T1> 
void SomeFunc(const T1& arg1); 
template<typename T1, typename T2> 
void SomeFunc(const T1& arg1, const T2& arg2); 
// etc... 

// And then somehow it would be called. This is just an example. I don't 
// know how to call it in a way that would work with variable number and 
// type of args. 
switch (argCount) 
{ 
case 0: 
    SomeFunc(); 
    break; 
case 1: 
    SomeFunc(arg1); 
    break; 
case 2: 
    SomeFunc(arg1, arg2); 
    break; 
case 3: 
    SomeFunc(arg1, arg2, arg3); 
    break; 
case 4: 
    SomeFunc(arg1, arg2, arg3, arg4); 
    break; 
case 5: 
    SomeFunc(arg1, arg2, arg3, arg4, arg5); 
    break; 
} 

Comment voulez-vous que cela soit possible? Stocker les args d'une façon qui peut être passée à la fonction template pour qu'elle connaisse le type de chaque argument ne semble pas possible, mais j'ai l'impression de ne pas penser à quelque chose.

Je ne peux pas non plus changer cette interface. C'est une fonction de tierce partie que je dois traiter. Donc, peu importe comment il est mis en œuvre, il doit éventuellement passer par SomeFunc(). IMPORTANT: Je le fais dans Visual Studio 2012, donc je suis plutôt limité sur les nouvelles fonctionnalités C++. Il peut faire un peu de C++ 11 mais c'est tout. Essayer de mettre à jour le projet vers une version plus récente mais pour l'instant c'est ce que j'ai à faire.

+0

Il semble que vous essayiez de mélanger la logique de compilation et la logique d'exécution. Les modèles sont des temps de compilation, et vous devez connaître le nombre de paramètres et leur type au moment de la compilation. Est-ce vraiment quelque chose que vous essayez d'accomplir? –

+0

Eh bien, si vous pouvez construire un tuple, vous pouvez utiliser [this] (https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer) pour activer le tuple dans un appel de fonction. – NathanOliver

+0

@TommyAndersen Cela semble exact. Je n'ai vraiment aucun moyen de contourner cela. J'essaye juste de faire une commande de console d'aide pour déclencher facilement des événements, qui peuvent avoir le nombre variable d'args et de types pour chacun. Si cela devient incontrôlable, je vais probablement me contenter de l'idée, ce qui pourrait sembler être le cas. – Shenjoku

Répondre

1
using basic_type = std::variant<int, float, bool, std::string>; 

using flat_arguments = std::vector<basic_type>; 

template<std::size_t...Ns> 
using packed_arguments = std::variant< std::array<basic_type, Ns>... >; 

template<class T, std::size_t...Ns> 
std::array<T, sizeof...(Ns)> pack_one(std::vector<T> n, std::index_sequence<Ns...>) { 
    return {{ std::move(n[Ns])... }}; 
} 

template<class T, std::size_t...Ns> 
std::optional<std::variant< std::array<T, Ns>... >> 
pack_all(std::vector<T> n, std::index_sequence<Ns...>) { 
    std::optional<std::variant< std::array<T, Ns>... >> retval; 
    if (n.size() >= sizeof...(Ns)) { return retval; } 
    (
    (
     (n.size()==Ns)? 
     void(retval.emplace(pack_one(std::move(n), std::make_index_sequence<Ns>{}): 
     void() 
    ),... 
); 
    return retval; 
} 

flat_arguments get_arguments(int argc, char const* const*argv); // write this 

auto invoke_somefunc = [](auto&&...args){ 
    return SomeFunc(decltype(args)(args)...); 
}; 

int main(int argc, char const*const* argv) { 
    auto args = get_arguments(argc, argv); 
    auto args_packed = pack_all(std::move(args), std::make_index_sequence<9>{}); 
    if (!args_packed) return -1; 
    std::visit([](auto&& args){ 
    std::apply([](auto&&...args){ 
     std::visit(invoke_somefunc, args...); 
    }, args); 
    }, args_packed); 
} 

qui devrait le faire. Probablement contient des fautes de frappe. .

boost a des types équivalents (variant et optional) qui pourraient remplacer l'utilisation std ci-dessus, avec quelques modifications.

L'extension du pli peut être remplacée par le hack de développement dans ou supérieur.

+0

Désolé, j'ai oublié de mentionner que je suis plutôt limité sur les nouvelles fonctionnalités de langue. Je le fais dans VS2012 donc je ne pense pas que la plupart de ceci est disponible. J'ai mis à jour le message original avec cette information. – Shenjoku

+0

@Shenjoku Oh, alors non, vous ne pouvez pas. Je veux dire, il y a le Turing Tar Pit, où vous pouvez faire n'importe quoi, mais vous seriez essentiellement en train d'étendre le travail ci-dessus manuellement dans un tas de code spaghetti. Il est beaucoup plus facile de mettre à jour votre compilateur C++ que de l'écrire dans le sous-langage de C++ supporté par VS 2012. Peut-être que si vous pouvez trouver une version ancienne de boost qui supporte la variante fonctionne encore dans VS 2012 et écrire manuellement les objets de fonction pour remplacer les lambdas et toute cette douleur. – Yakk