Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No matching function std::forward with lambdas

Consider the following code, inspired from Barry's answer to this 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;
}

It is not compiling because of line noted here, and I don't understand why:

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'

Replacing

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

by

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

apparently makes it work, but again, I don't understand why, and my goal is to achieve a perfect forwarding of the function.

like image 746
Vincent Avatar asked Oct 29 '22 12:10

Vincent


1 Answers

Apparently compilers are either bugged or allowed to declare variables captured by copy as const when the mutable specifier is not present.
Ironically, the following compiles with GCC, but it doesn't with 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, "!"); }();
}

To work around the issue in both cases you can do this:

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

That is, you can omit the mutable keyword, but you have to use the actual type of the copy of f within the lambda. Note that the original f is a non-const reference to a lambda function, thus F can differ from the type decltype(f) within the lambda.
This is valid in any case, even for mutable lambda. As an example:

#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);
}

In general, you should use the actual type of the captured variable instead of the type given in a surrounding context.
In the specific case, even though the compiler wrongly declares the captured variable as const, you can make it work without the mutable specifier as long as the function operator of f is const (that is your case, for f is main isn't mutable as well).

Another way to let your snippet work is this (as suggested in the comments to the question):

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

In this case, variables captured by copy cannot be forced to be const and the type is the expected one.
Anyway, I'd suggest to take the advice above even if you are planning to use the mutable specifier.


Note.
As discussed in this question the problem arose because of a bug of GCC. The suggestion to use decltype(f) still stands. It addresses also other types of problems and works for the specific case. Moreover, in case the bug is fixed, the code will continue to work properly.

like image 195
skypjack Avatar answered Nov 09 '22 15:11

skypjack