No matching function std::forward with lambdas

烈酒焚心 提交于 2020-01-13 10:28:11

问题


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.


回答1:


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.



来源:https://stackoverflow.com/questions/42044116/no-matching-function-stdforward-with-lambdas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!