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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With