Conversion de T en T&&

a marqué ce sujet comme résolu.

Bonjour.

J’ai récemment rencontré une erreur que je n’arrive pas à comprendre dans le cadre d’un projet C++. Mon compilateur ne parvient pas à convertir un type en référence universelle sur ce type (si j’ai bien compris) dans une fonction template.

Voici le code minimal:

#include <iostream>
#include <typeinfo>

struct Functor
{
    template <typename T>
    void operator()(void) const
    {
        std::cout << "Calling Functor<" << typeid(T).name() << ">::operator()(void) const\n";
    }
};

template <typename Fn, typename First, typename Second, typename... Others>
void execAll(Fn&& callback)
{
    execAll<Fn, First>(callback);
    execAll<Fn, Second, Others...>(callback);
}

template <typename Fn, typename Last>
void execAll(Fn&& callback)
{
    callback.template operator()<Last>();
}

int main(void)
{
    execAll<Functor, int, long, float, double, char>(Functor());
    return 0;
}

Ce qui m’intrigue particulièrement est que ce code fonctionne très bien quand je retire la mention "référence universelle" derrière Fn (transformant Fn&& en Fn).

Ma question est donc : pour quelle raison cette erreur a t-elle lieu et que puis-je faire pour que ma fonction accepte les références?

Tout d’abord, merci de ta réponse.

J’utilise g++ 6.3.0 qui m’indique ce message d’erreur:

main.cpp:28:60:   required from here
main.cpp:16:20: error: no matching function for call to ‘execAll(Functor&)’
  execAll<Fn, First>(callback);
  ~~~~~~~~~~~~~~~~~~^~~~~~~~~~
main.cpp:14:6: note: candidate: template<class Fn, class First, class Second, class ... Others> void execAll(Fn&&)
 void execAll(Fn&& callback)
      ^~~~~~~
main.cpp:14:6: note:   template argument deduction/substitution failed:
main.cpp:16:20: note:   couldn't deduce template parameter ‘Second’
  execAll<Fn, First>(callback);
  ~~~~~~~~~~~~~~~~~~^~~~~~~~~~
main.cpp:21:6: note: candidate: template<class Fn, class Last> void execAll(Fn&&)
 void execAll(Fn&& callback)
      ^~~~~~~
main.cpp:21:6: note:   template argument deduction/substitution failed:
main.cpp:16:20: note:   cannot convert ‘callback’ (type ‘Functor’) to type ‘Functor&&’
  execAll<Fn, First>(callback);
  ~~~~~~~~~~~~~~~~~~^~~~~~~~~~
main.cpp:17:32: error: no matching function for call to ‘execAll(Functor&)’
  execAll<Fn, Second, Others...>(callback);
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~
main.cpp:14:6: note: candidate: template<class Fn, class First, class Second, class ... Others> void execAll(Fn&&)
 void execAll(Fn&& callback)
      ^~~~~~~
main.cpp:14:6: note:   template argument deduction/substitution failed:
main.cpp:17:32: note:   cannot convert ‘callback’ (type ‘Functor’) to type ‘Functor&&’
  execAll<Fn, Second, Others...>(callback);
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~
main.cpp:21:6: note: candidate: template<class Fn, class Last> void execAll(Fn&&)
 void execAll(Fn&& callback)
      ^~~~~~~
main.cpp:21:6: note:   template argument deduction/substitution failed:
main.cpp:17:32: error: wrong number of template arguments (5, should be 2)
  execAll<Fn, Second, Others...>(callback);
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~

J’utilise aussi g++ 8 sur une autre machine (qui lui non-plus ne compile pas) et la même erreur apparaît.

Quant à clang, les messages sont

main.cpp:22:2: error: no matching function for call to 'bitsetExec'
        ecs::bitsetExec<6, Functor, int, long, float, double, char>(Functor(), std::bitset<6> { 0b000101 });
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./ecs.tpp:55:7: note: candidate function not viable: expects an l-value for 1st argument
        void bitsetExec(Fn& functor, std::bitset<N> const& bitset)
             ^
./ecs.tpp:62:7: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Last'
        void bitsetExec(Fn& functor, std::bitset<N> const& bitset)
             ^
1 error generated.

depuis le code original, ce qui ne change pas grand-chose.

+0 -0

Avec une référence universelle, Fn est soit de type Functor&, soit Functor&&. Puisque tu passes une variable à execAll, le type déduit serait Functor&, mais tu lui donnes Functor.

Donc tu passes une lvalue à une fonction qui attend un rvalue.

execAll<Fn&, First>(callback); // idem pour l'autre
// ou pour forcer la rvalue si besoin: std::forward ou un cast:
execAll<Fn, First>(static_cast<Fn&&>(callback));

Mais c’est le genre de fonction qui pourrait prendre une référence constante. L’idéal serait de ne pas mettre le type du foncteur, ce qui est possible avec les variables inlines et un foncteur intermédiaire.

Au passage, tu peux faire une seule fonction non-récursive avec les folds de c++17 ou une petite macro comme:

#define UNPACK(...)                   \
  (void)::std::initializer_list<int>{ \
    (void((__VA_ARGS__)), 0)...       \
  }

template <typename Fn, typename... Ts>
void execAll(Fn&& callback)
{
    UNPACK(callback.template operator()<Ts>());
}

explication ici

Note aussi que void en paramètre de fonction est inutile en C++.

+0 -0

@jo_link_noir Tout d’abord, merci de ta réponse et désolé de ne pas avoir pu répondre plus vite, mes débuts de semaine sont assez chargés.

Merci beaucoup, cette méthode marche parfaitement!

En ce qui concerne la récursion, merci de me parler de ces méthodes, elles me serviront sûrement un jour, mais ici, dans la fonction finale, seuls certains types sont exécutés (en fonction d’une condition évaluée à l’exécution), or la récursion me semble la meilleure solution dans ce cas (pour ne pas dire la seule).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte