4 votes

Aucune fonction correspondante std::forward avec lambdas

Considérons le code suivant, inspiré de la réponse de Barry à la question suivante ce question :

// Include
#include <tuple>
#include <utility>
#include <iostream>
#include <type_traits>

// Generic overload rank
template <std::size_t N> 
struct overload_rank 
: overload_rank<N - 1> 
{
};

// Default overload rank
template <> 
struct overload_rank<0> 
{
};

// Prepend argument to function
template <std::size_t N, class F>
auto prepend_overload_rank(F&& f) {
    using rank = overload_rank<N>;
    return [f = std::forward<F>(f)](rank, auto&&... args) -> decltype(auto) {
        return std::forward<F>(f)(std::forward<decltype(args)>(args)...); // here
    };
}

// Main
int main(int argc, char* argv[])
{
    auto f = [](int i){return i;};
    prepend_overload_rank<5>(f)(overload_rank<5>(), 1);
    return 0;
}

Il ne compile pas à cause de la ligne notée here et je ne comprends pas pourquoi :

With g++:
error: no matching function for call to 'forward<main(int, char**)::<lambda(int)>&>(const main(int, char**)::<lambda(int)>&)'
With clang:
error: no matching function for call to 'forward'

Remplacement de

return std::forward<F>(f)(std::forward<decltype(args)>(args)...); 

par

return f(std::forward<decltype(args)>(args)...); 

fait apparemment fonctionner le système, mais encore une fois, je ne comprends pas pourquoi, et mon objectif est de parvenir à une redirection parfaite de la fonction.

2voto

skypjack Points 5516

Apparemment, les compilateurs sont soit bogués, soit autorisés à déclarer les variables capturées par la copie en tant que const lorsque le mutable n'est pas présent.
Ironiquement, ce qui suit compile avec GCC, mais pas avec clang :

#include <type_traits>

int main(int argc, char* argv[]) {
    int i = 0;
    [j = i](){ static_assert(std::is_same<decltype(j), const int>::value, "!"); }();
}

Pour contourner le problème dans les deux cas, vous pouvez procéder comme suit :

return [f = std::forward<F>(f)](auto&&... args) -> decltype(auto) {
    return std::forward<decltype(f)>(f)(std::forward<decltype(args)>(args)...); // here
};

C'est-à-dire que vous pouvez omettre le mutable mais vous devez utiliser le type réel de la copie de l'application f à l'intérieur de la lambda. Notez que le original f est une référence non-const à une fonction lambda, donc F peut différer du type decltype(f) à l'intérieur de la lambda.
Ceci est valable dans tous les cas, même pour mutable lambda. A titre d'exemple :

#include <type_traits>
#include<utility>

struct S {};

template<typename T>
void f(T &&t) {
    [t = std::forward<T>(t)]()mutable{ static_assert(std::is_same<decltype(t), S>::value, "!"); }();
    // the following doesn't compile for T is S& that isn't the type of t within the lambda
    //[t = std::forward<T>(t)]()mutable{ static_assert(std::is_same<decltype(t), T>::value, "!"); }();
}

int main() {
    S s;
    f(s);
}

En général, vous devez utiliser le type réel de la variable capturée plutôt que le type donné dans un contexte environnant.
Dans le cas spécifique, même si le compilateur déclare à tort la variable capturée en tant que const vous pouvez le faire fonctionner sans le mutable tant que l'opérateur de fonction de f es const (c'est votre cas, pour f es main n'est pas mutable également).

Une autre façon de faire fonctionner votre extrait est la suivante (comme suggéré dans les commentaires de la question) :

return [f = std::forward<F>(f)](auto&&... args) mutable -> decltype(auto) {
    return std::forward<F>(f)(std::forward<decltype(args)>(args)...); // here
};

Dans ce cas, les variables capturées par copie ne peuvent pas être forcées à être const et le type est celui attendu.
Quoi qu'il en soit, je vous suggère de suivre les conseils ci-dessus même si vous envisagez d'utiliser le mutable spécificateur.


Note.
Comme indiqué dans cette question le problème est apparu à cause d'un bug de GCC. La suggestion d'utiliser decltype(f) est toujours d'actualité. Il traite également d'autres types de problèmes et fonctionne pour le cas spécifique. En outre, si le bogue est corrigé, le code continuera à fonctionner correctement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X