2017-01-24 1 views
1

Je suis en train de faire quelque chose de similaire à C++11 variable number of arguments, same specific type, mais j'ai mon propre type:nombre variable d'arguments, même type spécifique sans macro ou initialiseur liste

struct Foo 
{ 
    Foo(int) {} 
    Foo(int, int) {} 
}; 

avec un tas de

surcharge
void f() {} 
void f(const Foo&) {} 
void f(const Foo&, const Foo&) {} 
// etc. ... as many f() overloads as needed ... 

fonctionne comme vous le souhaitez: f(); f(1); f(1, 2); f(1, { 2, 3 });.

Au lieu de surcharge, je peux aussi utiliser std::initializer_list<> avec la syntaxe {} (comme suggéré here):

void g_(std::initializer_list<Foo>) {} 
g_({}); g_({ 1 }); g_({ 1, 2 }); g_({ 1, { 2, 3 } }); 

mais qui a un ensemble de {} (oui, il est juste deux caractères) supplémentaires. Pour correspondre à la syntaxe de f() exactement, utilisez une macro.

#define g(...) g_({__VA_ARGS__}) 
g(); g(1); g(1, 2); g(1,{ 2, 3 }); 

(Adaptation de la syntaxe de f() exactement pourrait être nécessaire en raison de l'héritage ou le code généré ... et it- arguably -Juste « semble mieux ».)

mais je ne peux pas trouver un moyen de faire un travail de modèle variadique

void h() { } 
template<typename... T> 
void h(const Foo&, T...) { } 

h(), h(1) et h(1, 2) travail, mais ne parvient pas à h(1, {2, 3}) compiler parce que le compilateur ne peut pas comprendre le type de {2, 3} comme il le peut avec f() et g_().

Existe-t-il un moyen de faire fonctionner f() sans plusieurs surcharges? Ou pour g() de travailler sans macro? g() est très à proximité (une seule fonction et pas de magie de modèle), mais il y a cette macro ...

+0

Où tracez-vous la ligne sur les surcharges "multiples"? En avoir deux ok? Les variadiques sont souvent associés à la récursion, où vous avez besoin d'au moins deux (le cas de récursivité et le cas de base) –

+0

@BenVoigt "unlimited"; 'g()' serait ma solution préférée s'il n'y avait pas de macro. (Légères modifications pour clarifier cela.) –

+1

'{2, 3}' n'a aucun type, et ne peut donc pas être déduit par déduction de modèle. – Jarod42

Répondre

4

{} nécessite d'initialiser quelque chose d'un type spécifique.

Les arguments variables C++ 11 nécessitent que vos types soient des types déduits.

Ce sont des exigences opposées.

Maintenant, je pourrais générer un objet avec un ensemble de () surcharges jusqu'à un grand nombre fini.

namespace details { 
    template<std::size_t, class T> 
    using ignore_index=T; 

    template<class T, class Count, class Base> 
    struct linear_overload_count; 
    template<class T, std::size_t I0, std::size_t...Is, class Base> 
    struct linear_overload_count<T, std::index_sequence<I0,Is...>, Base>: 
    linear_overload_count<T, std::index_sequence<Is...>, Base> 
    { 
    using linear_overload_count<T, std::index_sequence<Is...>, Base>::operator(); 
    using linear_overload_count<T, std::index_sequence<Is...>, Base>::linear_overload_count; 

    std::result_of_t< 
     Base const&(T const&, ignore_index<Is,T>const&...) 
    > 
    operator()(T const& t0, ignore_index<Is,T>const&...ts) const { 
     return Base::operator()(t0, ts...); 
    } 
    linear_overload_count()=default; 
    linear_overload_count(linear_overload_count const&)=default; 
    linear_overload_count(linear_overload_count &&)=default; 
    linear_overload_count& operator=(linear_overload_count const&)=default; 
    linear_overload_count& operator=(linear_overload_count &&)=default; 
    }; 
    template<class T, class Base> 
    struct linear_overload_count<T, std::index_sequence<>, Base>: 
    Base 
    { 
    using Base::Base; 
    linear_overload_count(Base&& b):Base(std::move(b)) {} 
    linear_overload_count(Base const& b):Base(b) {} 
    std::result_of_t< 
     Base const&() 
    > 
    operator()() const { 
     return Base::operator()(); 
    } 
    linear_overload_count()=default; 
    linear_overload_count(linear_overload_count const&)=default; 
    linear_overload_count(linear_overload_count &&)=default; 
    linear_overload_count& operator=(linear_overload_count const&)=default; 
    linear_overload_count& operator=(linear_overload_count &&)=default; 
    }; 
} 
template<class T, std::size_t N, class Base> 
using linear_overload_Ts = details::linear_overload_count<T, std::make_index_sequence<N>, Base>; 

auto count_args_impl = [](auto&&...args) { std::cout << sizeof...(args) << "\n"; }; 

struct bob { 
    int x,y; 
}; 

using count_bobs_t = linear_overload_Ts< bob, 3, decltype(count_args_impl) >; 
count_bobs_t const bobs = count_args_impl; 

int main() { 
    bobs(); 
    bobs({}, {}, {1,2}); 
} 

live example.

Maintenant, nous pouvons avoir jusqu'à 100 surcharges en bobs en changeant le numéro 3 au dessus de to 100.

Notez que si vous en atteignez plus de 100, your compiler will die. Cela peut être résolu avec un héritage d'arbre binaire au lieu d'un héritage linéaire, mais je ne peux pas être dérangé.

En outre, cette technique peut ralentir la compilation.

Notez que Base doit être un type. Vous pouvez utiliser un lambda comme ci-dessus pour transmettre à votre fonction de modèle (donnez-leur des noms différents), un objet de fonction manuel, ou quoi que ce soit d'autre.

L'utilisation de cette technique pour créer une fonction à la place d'un objet fonction n'est pas quelque chose que je peux résoudre sans l'aide d'un type nommé dans l'appel (utilisez ADL pour trouver la fonction générée). Les objets de fonction ne participent pas à la résolution de surcharge de la même manière que les fonctions, ce qui peut poser problème.

Cela semble aussi beaucoup de travail à faire avec l'ajout d'un ensemble supplémentaire de {}.

+0

Nice ... et ** WOW **! Oui, cela montre vraiment la simplicité de 'g _()', bien que vous ayez prouvé que cela peut vraiment être fait sans une macro. –

+0

@Dan Je n'utiliserais pas non plus la macro; Je préférerais chercher globalement et remplacer dans 99/100 cas comme ça. – Yakk

+0

aucun argument ... * en pratique * j'utiliserais 'g _()' ou 'f()', mais c'était une question "hmm ... je me demande". Vous avez fourni la réponse. :-) –

1

Si vous voulez un tas de Foo s, et que vous souhaitez autoriser des listes-init-contreventement, alors vous devriez faire:

void foo(std::initializer_list<Foo>); 

Oui, cela nécessite un jeu supplémentaire d'accolades. Non, vous ne devriez pas utiliser une macro pour pouvoir omettre ces deux caractères.


Vous ne pouvez pas utiliser un modèle variadique ici parce qu'un init liste contreventement est pas une expression et il n'a pas un type , donc il ne peut pas être déduit.

+2

Donc la réponse est "non, pas possible" et "ne fais pas ça!" –

+0

@Dan Yeah. Vous ne pouvez pas le faire avec un modèle. – Barry

+0

soin d'élaborer spécifiquement sur le "ne fais pas ça!"? En dehors de la macro générale, cela semble être un bon moyen de nettoyer un peu la syntaxe. –