1

J'essaie d'optimiser une fonction de niveau très bas et largement utilisée dans une application de calcul intensif. Disons que j'ai le type:C++ Ensembles multiples d'arguments de la fonction variadique

template<typename T, int N> 
class Elem {...}; 

Je voudrais écrire une fonction qui pourrait être appelé comme:

template<typename T, int N> 
void func(const Elem<T, N> & ... /*N elements*/, Elem<T, N> & ... /* N elements*/) 

Je suis à la recherche d'une approche pour laquelle je peux être Assurez-vous que les compilateurs seront en mesure de supprimer tout temporaire introduit par la signature de la fonction.

Les éléments seront généralement des éléments provenant de différents endroits d'un vecteur/tableau. Par exemple. :

Elem<float, 3> inputs[10]; 
Elem<float, 3> outputs[10]; 
... 
func(input[4], input[2], input[9], output[6], output[8], output[1]); 

La réponse est peut-être des listes de initialiseur mais je crains qu'il pourrait avoir certains frais généraux ...

NB: les indices de indirection ci-dessus sont tous compilent temps fonction calculée et sont courtes distances.


EDIT

En fait, ce que je voudrais serait quelque chose comme:

template<typename... T, int N> 
void func(const Elem<T, N>&... inputs, const Elem<T, N>&... outputs) 
{ 
    static_assert(sizeof...(inputs) == N, "invalid number of arguments"); 
    static_assert(sizeof...(outputs) == N, "invalid number of arguments"); 
    static_assert(std::is_same<std::integral_constant<int N>...>::value, "invalid arguments"); 
} 

Mais je ne peux pas avoir ce code pour compiler sur VS2017. La réponse peut être C++ 17.

+1

Arrêtez la crainte des frais généraux et commencer à mesurer. – nwp

+3

quels temporaires craignez-vous? Je ne vois que des références en tant que paramètres – user463035818

+0

C++ 11, C++ 14 ou C++ 17? – max66

Répondre

2

Je voudrais écrire une fonction qui pourrait être appelé comme:

template<typename T, int N> 
void func(const Elem<T, N> & ... /*N elements*/, 
      Elem<T, N> & ... /* N elements*/) 

En ce que je sais, ce que vous demandez est pas facile d'exprimer la langue disponible.

Le mieux que je peux imaginer, est d'écrire la fonction func() comme suit

template <typename ... Es> 
typename std::enable_if<checkElems<Es...>::value>::type 
    func (Es & ... es) 
{ 
    using type = typename checkElems<Es ...>::type; // former T 

    constexpr std::size_t num { sizeof...(Es) >> 1 }; // former N 

    // ... 
} 

func() reçoit une liste (Es & ... es) des arguments et la fonction est SFINAE activée uniquement si la liste relative des types (Es ...) satisfaire une liste d'exigences implémentées dans les caractères de type personnalisés checkElems (voir l'exemple suivant).

Ainsi, le chèque checkElems suivant que:

  • il y a au moins un type d'argument
  • le premier argument est sous la forme Elem<T, N> const
  • le nombre de type d'argument est exactement 2 * N
  • la premiers N types sont égaux les uns aux autres
  • les types suivants N sont égaux les uns aux autres
  • ajoutant const les N types suivants, ils sont égaux à la première N
  • sans ajouter const, les N types suivants sont différents pour la première N

Inside func() vous êtes en mesure de utilisez type (voir l'exemple), c'est le type T dans Elem<T, N>, et num, c'est la valeur N dans Elem<T, N>.

Vous pouvez faire ces contrôles mais je ne sais pas si c'est une bonne idée.

Un exemple compilable

#include <tuple> 
#include <type_traits> 

template<typename T, std::size_t N> 
struct Elem {}; 

template <typename> 
struct extrElem; 

template <typename T, std::size_t N> 
struct extrElem<Elem<T, N> const> 
{ 
    using type = T; 

    static constexpr std::size_t num { N }; 
}; 

template <std::size_t, std::size_t, typename ...> 
struct extrTypes; 

template <std::size_t Skip, std::size_t Num, typename ... Es, typename T0, 
      typename ... Ts> 
struct extrTypes<Skip, Num, std::tuple<Es...>, T0, Ts...> 
{ using type = typename extrTypes< 
     Skip-1U, Num, std::tuple<Es...>, Ts...>::type; }; 

template <std::size_t Num, typename ... Es, typename T0, typename ... Ts> 
struct extrTypes<0U, Num, std::tuple<Es...>, T0, Ts...> 
{ using type = typename extrTypes< 
     0U, Num-1U, std::tuple<Es..., T0>, Ts...>::type; }; 

template <typename ... Es, typename T0, typename ... Ts> 
struct extrTypes<0U, 0U, std::tuple<Es...>, T0, Ts...> 
{ using type = std::tuple<Es...>; }; 

template <typename ... Es> 
struct extrTypes<0U, 0U, std::tuple<Es...>> 
{ using type = std::tuple<Es...>; }; 

template <typename> 
struct sameContTypes : public std::false_type 
{ }; 

template <template <typename ...> class C, typename T0, typename ... Ts> 
struct sameContTypes<C<T0, Ts...>> 
    : public std::is_same<C<T0, Ts...>, C<Ts..., T0>> 
{ }; 

template <typename E0, typename ... Es> 
struct checkElems 
{ 
    static constexpr std::size_t num { extrElem<E0>::num }; 

    using type = typename extrElem<E0>::type; 
    using lt1 = typename extrTypes<0U, num, std::tuple<>, E0, Es...>::type; 
    using lt2 = typename extrTypes<num, num, std::tuple<>, E0, Es...>::type; 

    static constexpr bool value { 
     ((num << 1) == 1U + sizeof...(Es)) 
     && sameContTypes<lt1>::value 
     && sameContTypes<lt2>::value 
     && (true == std::is_same<E0, 
      typename std::tuple_element<0U, lt2>::type const>::value) 
     && (false == std::is_same<E0, 
      typename std::tuple_element<0U, lt2>::type>::value) }; 
}; 

template <typename ... Es> 
typename std::enable_if<checkElems<Es...>::value>::type 
    func (Es & ... es) 
{ 
    using type = typename checkElems<Es ...>::type; // former T 

    constexpr std::size_t num { sizeof...(Es) >> 1 }; // former N 

    // ... 
} 

int main() 
{ 
    Elem<int, 3>  ei3; 
    Elem<int, 4>  ei4; 
    Elem<int, 3> const eci3; 
    Elem<int, 4> const eci4; 

    func(eci3, eci3, eci3, ei3, ei3, ei3); // compile 
    //func(eci3, eci3, eci3, ei3, eci3, ei3); // compilation error 
    //func(eci3, eci3, eci3, ei3, ei3, ei4); // compilation error 
    //func(eci3, eci3, eci4, ei3, ei3, ei3); // compilation error 
    //func(eci4, eci4, eci4, ei4, ei4, ei4); // compilation error 
    //func(eci4, eci4, eci4, eci4, ei4, ei4, ei4); // compilation error 
    func(eci4, eci4, eci4, eci4, ei4, ei4, ei4, ei4); // compile 
} 
+1

Wow! C'est impressionnant ! Je vais essayer d'étendre l'approche extrTypes pour diviser les arguments réels en deux ensembles. – user1524126

0

Je ne sais pas, si je reçois la question, mais vous pouvez faire

template<typename... T, int... N> 
void func(const Elem<T, N>&... elems) 

si vous voulez que votre fonction de prendre un certain nombre de Elem<>. Si vous avez besoin de les limiter à un seul T (par exemple Elem<int,> et Elem<float,> ne peuvent pas être passés ensemble), il suffit d'utiliser

template<typename T, int... N> 
void func(const Elem<T, N>&... elems) 

Si vous avez besoin de limiter le nombre d'arguments, il suffit d'utiliser un

static_assert(sizeof...(elems) <= 6, "..."); 

à l'intérieur du corps de la fonction. Si N doit être égale pour tous les éléments et le nombre d'arguments devraient être 2 * N, utilisez:

template<typename... T, int N> 
void func(const Elem<T, N>&... elems) 
{ 
    static_assert(sizeof...(elems) == 2*N, "invalid number of arguments"); 
} 
+0

Merci pour votre réponse. Je besoin de deux séries d'arguments: template vide func (const Elem & ... entrées, const Elem & ... sorties) { static_assert (sizeof. .. (entrées) == N, "nombre d'arguments invalide"); static_assert (sizeof ...(sorties) == N, "nombre d'arguments invalide"); } – user1524126

+0

@ user1524126 - Je demande à voir si je comprends bien: vous avez besoin d'arguments '2 * N'; le premier 'N' (argument d'entrée) est de type' Elem const & '(je veux dire: même' N' du nombre d'arguments et 'T' égaux dans chaque argument) et le dernier' N' est de type 'Elem & & '(tout de même' N' et même 'T'). Correct? – max66

+0

@ max66: exactement! – user1524126

3

je passerais chaque ensemble comme un tuple de références, que vous pouvez faire avec std::tie.

Dans la plupart des cas, il n'y aura pas de surcharge du tout car le compilateur verra à travers toute la construction du tuple.

exemple:

#include <tuple> 
#include <type_traits> 


template<class T, std::size_t N> 
struct Elem { 
    T value() const { return val; } 

    T val; 
}; 

Elem<float, 3> input[10]; 
Elem<float, 3> output[10]; 


namespace detail { 
    template<typename T, typename F, std::size_t... Is> 
    constexpr auto tuple_foreach(T&& tup, F& f, std::index_sequence<Is...>) { 
     using expand = int[]; 
     void(expand{0, 
       (f(std::get<Is>(std::forward<T>(tup))), 0)... 
     }) 
     ; 
    } 
} 

template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>> 
constexpr auto tuple_foreach(T&& tup, F f) { 
    return detail::tuple_foreach(
     std::forward<T>(tup), f, 
     std::make_index_sequence<TupSize>{} 
    ); 
} 

template<class Set1, class Set2> 
auto func(Set1 set1, Set2 set2) 
{ 
    constexpr auto N1 = std::tuple_size<Set1>::value; 
    constexpr auto N2 = std::tuple_size<Set2>::value; 
    static_assert(N1 == N2, ""); 

    // now do things with std::get<0 ... N-1>(set1) and 
    // std::get<0 ... N-1>(set2); 

    using result_type = std::decay_t<decltype(std::get<0>(set1).value())>; 

    // let's compute the sum of the inputs 
    result_type result = 0; 
    tuple_foreach(set1, 
         [&](auto&& elem) 
         { 
          result += elem.value(); 
         }); 
    tuple_foreach(set2, 
         [&](auto&& elem) 
         { 
          result += elem.value(); 
         }); 

    return result; 
} 

void emit(float); 

int main() 
{ 
    auto x = func(std::tie(input[4], input[2], input[9]), 
        std::tie(output[6], output[8], output[1])); 
    emit(x); 
} 

assemblage émis avec réglage compilateur -O2:

main: 
    pxor xmm0, xmm0 
    sub rsp, 8 
    addss xmm0, DWORD PTR input[rip+16] 
    addss xmm0, DWORD PTR input[rip+8] 
    addss xmm0, DWORD PTR input[rip+36] 
    addss xmm0, DWORD PTR output[rip+24] 
    addss xmm0, DWORD PTR output[rip+32] 
    addss xmm0, DWORD PTR output[rip+4] 
    call emit(float) 
    xor eax, eax 
    add rsp, 8 
    ret 

Impossible d'obtenir beaucoup plus efficace que cela.