2017-01-07 1 views
7

Supposons que j'ai une classe de modèlecomportement de référence avec Forwarding types définis

template <typename T> class foo; 
template <typename... Args> 
struct foo<std::tuple<Args...>> { 
    std::tuple<Args...> t; 
    foo(Args&&... args): t{std::forward<Args>(args)...} { } 
}; 

Je comprends que dans ce cas Args&&... sont des références rvalue, et je aurais pu tout aussi bien écrit std::move au lieu de std::forward.

Je peux aussi avoir un constructeur avec des références lvalue, comme si

foo(const Args&... args): t{args...} { } 

La question est de savoir s'il est possible d'obtenir le même comportement que les références de transfert, mais pour des types précis? La raison pour laquelle je veux que c'est que je puisse utiliser la syntaxe comme

foo bar({. . .}, {. . .}, . . ., {. . .}); 

Cela fonctionne si je définis le constructeur foo(Args&&... args), mais ne permet pas d'un scénario mixte, où je veux initialiser certains des éléments de tuple membres avec listes d'initialisation accolées entre accolades et d'autres copiés à partir d'instances d'objet préexistantes.

+0

Ce n'est pas une "référence de renvoi" s'il ne s'agit pas d'une référence de renvoi. (Les références de transfert requièrent la déduction de l'argument template.) –

Répondre

2

Sure; il y a un moyen simple et sophistiqué.

La manière de fantaisie que je détaillerai ci-dessous. D'abord le moyen simple: prendre en valeur.

template <typename... Args> 
struct foo<std::tuple<Args...>> { 
    std::tuple<Args...> t; 
    foo(Args... args): t{std::forward<Args>(args)...} { } 
}; 

Vraiment, faites-le. Forward est utilisé pour faire la bonne chose si Args contient une référence. La prise en valeur ajoute un mouvement au transfert parfait, mais réduit de façon exponentielle le besoin de surcharges.


Ceci est la manière de fantaisie. Nous effaçons la construction d'effacement:

template<class T> 
struct make_it { 
    using maker=T(*)(void*); 
    maker f; 
    void* args; 
    // make from move 
    make_it(T&& t): 
    f([](void* pvoid)->T{ 
     return std::move(*static_cast<T*>(pvoid)); 
    }), 
    args(std::addressof(t)) 
    {} 
    // make from copy 
    make_it(T const& t): 
    f([](void* pvoid)->T{ 
     return *(T const*)(pvoid); 
    }), 
    args(std::addressof(t)) 
    {} 
    operator T()&&{return std::move(*this)();} 
    T operator()()&&{ return f(args); } 
}; 

Ce type efface la construction par la copie ou le mouvement.

template <typename... Args> 
struct foo<std::tuple<Args...>> { 
    std::tuple<Args...> t; 
    foo(make_it<Args>... args): t{std::move(args)()...} { } 
}; 

Ce n'est pas parfaitement transparent, mais c'est aussi proche que possible.

Le double {{}} est requis au lieu d'un seul. C'est une conversion définie par l'utilisateur, donc une autre ne sera pas implicitement faite. Nous pourrions ajouter un cteur universel: »

// make from universal 
    template<class U> 
    make_it(U&& u): 
    f([](void* pvoid)->T{ 
     return std::forward<U>(*(U*)(pvoid)); 
    }), 
    args(std::addressof(u)) 
    {} 

qui fonctionne mieux si l'on ajoute une tétine de sfinae qui U&& peut être utilisé pour construire implicitement T.

Il a quelques avantages, mais ils sont marginaux plutôt que de prendre en valeur. Par exemple, en C++ 17, les types non mobiles peuvent être construits en avant dans certains cas.

+0

Pourquoi l'unaire '+ 'est-il appliqué à lambdas? La coercition explicite au pointeur semble être superflue ici. –

+0

@yuri habitude principalement? Je suppose que l'existence de Msvc cassé en fait une mauvaise idée. – Yakk

+0

@Yakk C'est génial. Merci pour la réponse de fantaisie. Je dois avouer que je ne comprends pas encore tout le truc en un coup d'œil. Que voulez-vous dire par "ajouter une tache sfinae que' U && 'peut être utilisée pour construire implicitement' T' "?C'est la seule fonctionnalité que je demande ici. Cela peut-il être utilisé directement avec le constructeur de 'foo', s'il est écrit comme' template foo (U && ... u): t {std :: forward (u) ...} {} '? – SU3