Why can't argument be forwarded inside lambda without mutable?Why can't variables be declared in a switch statement?Why are Python lambdas useful?Why does C++11's lambda require “mutable” keyword for capture-by-value, by default?Does lambda capture support variadic template argumentsCan't compile basic C++ program with TBB and lambdaCompiling with gcc fails if using lambda function for QObject::connect()Why std::bind cannot resolve function overloads with multiple arguments?Create shared packaged_task that accepts a parameter with forwardingIntel compiler failing to compile variadic lambda capture with multiple argumentsPassing variadic template to pthread_create
Do high-wing aircraft represent more difficult engineering challenges than low-wing aircraft?
Resistor Selection to retain same brightness in LED PWM circuit
FIFO data structure in pure C
Why doesn't Iron Man's action affect this person in Endgame?
Divisor Rich and Poor Numbers
Why do academics prefer Mac/Linux?
A latin word for "area of interest"
Solenoid fastest possible release - for how long should reversed polarity be applied?
What technology would Dwarves need to forge titanium?
Is there any deeper thematic meaning to the white horse that Arya finds in The Bells (S08E05)?
Single word that parallels "Recent" when discussing the near future
Is it standard for US-based universities to consider the ethnicity of an applicant during PhD admissions?
Can I pay my credit card?
What formula to chose a nonlinear formula?
Write electromagnetic field tensor in terms of four-vector potential
Why is Drogon so much better in battle than Rhaegal and Viserion?
How can we delete item permanently without storing in Recycle Bin?
How was the blinking terminal cursor invented?
Is Precocious Apprentice enough for Mystic Theurge?
How to know the path of a particular software?
How to pass store code to custom URL in magento 2
Do we see some Unsullied doing this in S08E05?
How does the Heat Metal spell interact with a follow-up Frostbite spell?
Was the dragon prowess intentionally downplayed in S08E04?
Why can't argument be forwarded inside lambda without mutable?
Why can't variables be declared in a switch statement?Why are Python lambdas useful?Why does C++11's lambda require “mutable” keyword for capture-by-value, by default?Does lambda capture support variadic template argumentsCan't compile basic C++ program with TBB and lambdaCompiling with gcc fails if using lambda function for QObject::connect()Why std::bind cannot resolve function overloads with multiple arguments?Create shared packaged_task that accepts a parameter with forwardingIntel compiler failing to compile variadic lambda capture with multiple argumentsPassing variadic template to pthread_create
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
In the below program, when mutable
is not used, the program fails to compile.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
//q.emplace([=]() // this fails
q.emplace([=]() mutable //this works
func(std::forward<Args>(args)...);
);
int main()
auto f1 = [](int a, int b) std::cout << a << b << "n"; ;
auto f2 = [](double a, double b) std::cout << a << b << "n";;
enqueue(f1, 10, 20);
enqueue(f2, 3.14, 2.14);
return 0;
This is the compiler error
lmbfwd.cpp: In instantiation of ‘enqueue(T&&, Args&& ...)::<lambda()> [with T = main()::<lambda(int, int)>&; Args = int, int]’:
lmbfwd.cpp:11:27: required from ‘struct enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]::<lambda()>’
lmbfwd.cpp:10:2: required from ‘void enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]’
lmbfwd.cpp:18:20: required from here
lmbfwd.cpp:11:26: error: no matching function for call to ‘forward<int>(const int&)’
func(std::forward<Args>(args)...);
I am not able to understand why argument forwarding fails without mutable
.
Besides, if I pass a lambda with string as argument, mutable
is not required and program works.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
//works without mutable
q.emplace([=]()
func(std::forward<Args>(args)...);
);
void dequeue()
while (!q.empty())
auto f = std::move(q.front());
q.pop();
f();
int main()
auto f3 = [](std::string s) std::cout << s << "n"; ;
enqueue(f3, "Hello");
dequeue();
return 0;
Why is mutable required in case of int double
and not in case of string
? What is the difference between these two ?
c++ c++11 lambda language-lawyer
add a comment |
In the below program, when mutable
is not used, the program fails to compile.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
//q.emplace([=]() // this fails
q.emplace([=]() mutable //this works
func(std::forward<Args>(args)...);
);
int main()
auto f1 = [](int a, int b) std::cout << a << b << "n"; ;
auto f2 = [](double a, double b) std::cout << a << b << "n";;
enqueue(f1, 10, 20);
enqueue(f2, 3.14, 2.14);
return 0;
This is the compiler error
lmbfwd.cpp: In instantiation of ‘enqueue(T&&, Args&& ...)::<lambda()> [with T = main()::<lambda(int, int)>&; Args = int, int]’:
lmbfwd.cpp:11:27: required from ‘struct enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]::<lambda()>’
lmbfwd.cpp:10:2: required from ‘void enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]’
lmbfwd.cpp:18:20: required from here
lmbfwd.cpp:11:26: error: no matching function for call to ‘forward<int>(const int&)’
func(std::forward<Args>(args)...);
I am not able to understand why argument forwarding fails without mutable
.
Besides, if I pass a lambda with string as argument, mutable
is not required and program works.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
//works without mutable
q.emplace([=]()
func(std::forward<Args>(args)...);
);
void dequeue()
while (!q.empty())
auto f = std::move(q.front());
q.pop();
f();
int main()
auto f3 = [](std::string s) std::cout << s << "n"; ;
enqueue(f3, "Hello");
dequeue();
return 0;
Why is mutable required in case of int double
and not in case of string
? What is the difference between these two ?
c++ c++11 lambda language-lawyer
2
As an aside, in the second example you are not forwardingstd::string
, you are forwardingconst char*
. It doesn't matter what type the function takes, but what type arguments are passed toenqueue
. My guess is, the argument beingconst
from the start somehow makes a difference, but I'm not sure how.
– Igor Tandetnik
May 5 at 14:23
4
And indeed, it does fail if you make itenqueue(f3, std::string("Hello"));
– Igor Tandetnik
May 5 at 14:24
1
...yes, or"Hello"s
(using string literals)
– andreee
May 5 at 14:25
@IgorTandetnik The direction is right: if you pass aconst
argument, the error disappears. See for example godbolt.org/z/EdY4Mn where I just introduced some empty classFoo
. If you remove the constness ofFoo
, it fails to compile. Still not sure why, though...
– andreee
May 5 at 14:29
It may be enlightening (although pointless) to realize that usingstd::forward<const Args>
also fixes the compile error.
– Vaughn Cato
May 5 at 17:14
add a comment |
In the below program, when mutable
is not used, the program fails to compile.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
//q.emplace([=]() // this fails
q.emplace([=]() mutable //this works
func(std::forward<Args>(args)...);
);
int main()
auto f1 = [](int a, int b) std::cout << a << b << "n"; ;
auto f2 = [](double a, double b) std::cout << a << b << "n";;
enqueue(f1, 10, 20);
enqueue(f2, 3.14, 2.14);
return 0;
This is the compiler error
lmbfwd.cpp: In instantiation of ‘enqueue(T&&, Args&& ...)::<lambda()> [with T = main()::<lambda(int, int)>&; Args = int, int]’:
lmbfwd.cpp:11:27: required from ‘struct enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]::<lambda()>’
lmbfwd.cpp:10:2: required from ‘void enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]’
lmbfwd.cpp:18:20: required from here
lmbfwd.cpp:11:26: error: no matching function for call to ‘forward<int>(const int&)’
func(std::forward<Args>(args)...);
I am not able to understand why argument forwarding fails without mutable
.
Besides, if I pass a lambda with string as argument, mutable
is not required and program works.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
//works without mutable
q.emplace([=]()
func(std::forward<Args>(args)...);
);
void dequeue()
while (!q.empty())
auto f = std::move(q.front());
q.pop();
f();
int main()
auto f3 = [](std::string s) std::cout << s << "n"; ;
enqueue(f3, "Hello");
dequeue();
return 0;
Why is mutable required in case of int double
and not in case of string
? What is the difference between these two ?
c++ c++11 lambda language-lawyer
In the below program, when mutable
is not used, the program fails to compile.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
{
//q.emplace([=]() // this fails
q.emplace([=]() mutable //this works
func(std::forward<Args>(args)...);
);
int main()
auto f1 = [](int a, int b) std::cout << a << b << "n"; ;
auto f2 = [](double a, double b) std::cout << a << b << "n";;
enqueue(f1, 10, 20);
enqueue(f2, 3.14, 2.14);
return 0;
This is the compiler error
lmbfwd.cpp: In instantiation of ‘enqueue(T&&, Args&& ...)::<lambda()> [with T = main()::<lambda(int, int)>&; Args = int, int]’:
lmbfwd.cpp:11:27: required from ‘struct enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]::<lambda()>’
lmbfwd.cpp:10:2: required from ‘void enqueue(T&&, Args&& ...) [with T = main()::<lambda(int, int)>&; Args = int, int]’
lmbfwd.cpp:18:20: required from here
lmbfwd.cpp:11:26: error: no matching function for call to ‘forward<int>(const int&)’
func(std::forward<Args>(args)...);
I am not able to understand why argument forwarding fails without mutable
.
Besides, if I pass a lambda with string as argument, mutable
is not required and program works.
#include <iostream>
#include <queue>
#include <functional>
std::queue<std::function<void()>> q;
template<typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
//works without mutable
q.emplace([=]()
func(std::forward<Args>(args)...);
);
void dequeue()
while (!q.empty())
auto f = std::move(q.front());
q.pop();
f();
int main()
auto f3 = [](std::string s) std::cout << s << "n"; ;
enqueue(f3, "Hello");
dequeue();
return 0;
Why is mutable required in case of int double
and not in case of string
? What is the difference between these two ?
c++ c++11 lambda language-lawyer
c++ c++11 lambda language-lawyer
edited May 5 at 14:26
Swordfish
11.8k21539
11.8k21539
asked May 5 at 14:11
bornfreebornfree
8731923
8731923
2
As an aside, in the second example you are not forwardingstd::string
, you are forwardingconst char*
. It doesn't matter what type the function takes, but what type arguments are passed toenqueue
. My guess is, the argument beingconst
from the start somehow makes a difference, but I'm not sure how.
– Igor Tandetnik
May 5 at 14:23
4
And indeed, it does fail if you make itenqueue(f3, std::string("Hello"));
– Igor Tandetnik
May 5 at 14:24
1
...yes, or"Hello"s
(using string literals)
– andreee
May 5 at 14:25
@IgorTandetnik The direction is right: if you pass aconst
argument, the error disappears. See for example godbolt.org/z/EdY4Mn where I just introduced some empty classFoo
. If you remove the constness ofFoo
, it fails to compile. Still not sure why, though...
– andreee
May 5 at 14:29
It may be enlightening (although pointless) to realize that usingstd::forward<const Args>
also fixes the compile error.
– Vaughn Cato
May 5 at 17:14
add a comment |
2
As an aside, in the second example you are not forwardingstd::string
, you are forwardingconst char*
. It doesn't matter what type the function takes, but what type arguments are passed toenqueue
. My guess is, the argument beingconst
from the start somehow makes a difference, but I'm not sure how.
– Igor Tandetnik
May 5 at 14:23
4
And indeed, it does fail if you make itenqueue(f3, std::string("Hello"));
– Igor Tandetnik
May 5 at 14:24
1
...yes, or"Hello"s
(using string literals)
– andreee
May 5 at 14:25
@IgorTandetnik The direction is right: if you pass aconst
argument, the error disappears. See for example godbolt.org/z/EdY4Mn where I just introduced some empty classFoo
. If you remove the constness ofFoo
, it fails to compile. Still not sure why, though...
– andreee
May 5 at 14:29
It may be enlightening (although pointless) to realize that usingstd::forward<const Args>
also fixes the compile error.
– Vaughn Cato
May 5 at 17:14
2
2
As an aside, in the second example you are not forwarding
std::string
, you are forwarding const char*
. It doesn't matter what type the function takes, but what type arguments are passed to enqueue
. My guess is, the argument being const
from the start somehow makes a difference, but I'm not sure how.– Igor Tandetnik
May 5 at 14:23
As an aside, in the second example you are not forwarding
std::string
, you are forwarding const char*
. It doesn't matter what type the function takes, but what type arguments are passed to enqueue
. My guess is, the argument being const
from the start somehow makes a difference, but I'm not sure how.– Igor Tandetnik
May 5 at 14:23
4
4
And indeed, it does fail if you make it
enqueue(f3, std::string("Hello"));
– Igor Tandetnik
May 5 at 14:24
And indeed, it does fail if you make it
enqueue(f3, std::string("Hello"));
– Igor Tandetnik
May 5 at 14:24
1
1
...yes, or
"Hello"s
(using string literals)– andreee
May 5 at 14:25
...yes, or
"Hello"s
(using string literals)– andreee
May 5 at 14:25
@IgorTandetnik The direction is right: if you pass a
const
argument, the error disappears. See for example godbolt.org/z/EdY4Mn where I just introduced some empty class Foo
. If you remove the constness of Foo
, it fails to compile. Still not sure why, though...– andreee
May 5 at 14:29
@IgorTandetnik The direction is right: if you pass a
const
argument, the error disappears. See for example godbolt.org/z/EdY4Mn where I just introduced some empty class Foo
. If you remove the constness of Foo
, it fails to compile. Still not sure why, though...– andreee
May 5 at 14:29
It may be enlightening (although pointless) to realize that using
std::forward<const Args>
also fixes the compile error.– Vaughn Cato
May 5 at 17:14
It may be enlightening (although pointless) to realize that using
std::forward<const Args>
also fixes the compile error.– Vaughn Cato
May 5 at 17:14
add a comment |
1 Answer
1
active
oldest
votes
A non-mutable
lambda generates a closure type with an implicit const
qualifier on its operator()
overload.
std::forward
is a conditional move: it is equivalent to std::move
when the provided template argument is not an lvalue reference. It is defined as follows:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(See: https://en.cppreference.com/w/cpp/utility/forward).
Let's simplify your snippet to:
#include <utility>
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[=] func(std::forward<Args>(args)...); ;
int main()
enqueue([](int) , 10);
The error produced by clang++ 8.x
is:
error: no matching function for call to 'forward'
[=] func(std::forward<Args>(args)...); ;
^~~~~~~~~~~~~~~~~~
note: in instantiation of function template specialization 'enqueue<(lambda at wtf.cpp:11:13), int>' requested here
enqueue([](int) , 10);
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
^
In the snippet above:
Args
isint
and refers to the type outside of the lambda.args
refers to the member of the closure synthesized via lambda capture, and isconst
due to the lack ofmutable
.
Therefore the std::forward
invocation is...
std::forward<int>(/* `const int&` member of closure */)
...which doesn't match any existing overload of std::forward
. There is a mismatch between the template argument provided to forward
and its function argument type.
Adding mutable
to the lambda makes args
non-const
, and a suitable forward
overload is found (the first one, which moves its argument).
By using C++20 pack-expansion captures to "rewrite" the name of args
, we can avoid the mismatch mentioned above, making the code compile even without mutable
:
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[func, ...xs = args] func(std::forward<decltype(xs)>(xs)...); ;
live example on godbolt.org
Why is
mutable
required in case ofint double
and not in case ofstring
? What is the difference between these two ?
This is a fun one - it works because you're not actually passing a std::string
in your invocation:
enqueue(f3, "Hello");
// ^~~~~~~
// const char*
If you correctly match the type of the argument passed to enqueue
to the one accepted by f3
, it will stop working as expected (unless you use mutable
or C++20 features):
enqueue(f3, std::string"Hello");
// Compile-time error.
To explain why the version with const char*
works, let's again look at a simplified example:
template <typename T>
void enqueue(T&& func, const char (&arg)[6])
[=] func(std::forward<const char*>(arg)); ;
int main()
enqueue([](std::string) , "Hello");
Args
is deduced as const char(&)[6]
. There is a matching forward
overload:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
After substitution:
template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
This simply returns t
, which is then used to construct the std::string
.
2
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
2
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
2
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
1
Where doesconst char*
come from? He's passing around a reference soArgs
isconst char(&)[6]
so it'sstd::forward<const char(&)[6]>(arg)
.
– 0x499602D2
May 5 at 17:15
|
show 2 more comments
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55992834%2fwhy-cant-argument-be-forwarded-inside-lambda-without-mutable%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
A non-mutable
lambda generates a closure type with an implicit const
qualifier on its operator()
overload.
std::forward
is a conditional move: it is equivalent to std::move
when the provided template argument is not an lvalue reference. It is defined as follows:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(See: https://en.cppreference.com/w/cpp/utility/forward).
Let's simplify your snippet to:
#include <utility>
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[=] func(std::forward<Args>(args)...); ;
int main()
enqueue([](int) , 10);
The error produced by clang++ 8.x
is:
error: no matching function for call to 'forward'
[=] func(std::forward<Args>(args)...); ;
^~~~~~~~~~~~~~~~~~
note: in instantiation of function template specialization 'enqueue<(lambda at wtf.cpp:11:13), int>' requested here
enqueue([](int) , 10);
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
^
In the snippet above:
Args
isint
and refers to the type outside of the lambda.args
refers to the member of the closure synthesized via lambda capture, and isconst
due to the lack ofmutable
.
Therefore the std::forward
invocation is...
std::forward<int>(/* `const int&` member of closure */)
...which doesn't match any existing overload of std::forward
. There is a mismatch between the template argument provided to forward
and its function argument type.
Adding mutable
to the lambda makes args
non-const
, and a suitable forward
overload is found (the first one, which moves its argument).
By using C++20 pack-expansion captures to "rewrite" the name of args
, we can avoid the mismatch mentioned above, making the code compile even without mutable
:
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[func, ...xs = args] func(std::forward<decltype(xs)>(xs)...); ;
live example on godbolt.org
Why is
mutable
required in case ofint double
and not in case ofstring
? What is the difference between these two ?
This is a fun one - it works because you're not actually passing a std::string
in your invocation:
enqueue(f3, "Hello");
// ^~~~~~~
// const char*
If you correctly match the type of the argument passed to enqueue
to the one accepted by f3
, it will stop working as expected (unless you use mutable
or C++20 features):
enqueue(f3, std::string"Hello");
// Compile-time error.
To explain why the version with const char*
works, let's again look at a simplified example:
template <typename T>
void enqueue(T&& func, const char (&arg)[6])
[=] func(std::forward<const char*>(arg)); ;
int main()
enqueue([](std::string) , "Hello");
Args
is deduced as const char(&)[6]
. There is a matching forward
overload:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
After substitution:
template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
This simply returns t
, which is then used to construct the std::string
.
2
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
2
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
2
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
1
Where doesconst char*
come from? He's passing around a reference soArgs
isconst char(&)[6]
so it'sstd::forward<const char(&)[6]>(arg)
.
– 0x499602D2
May 5 at 17:15
|
show 2 more comments
A non-mutable
lambda generates a closure type with an implicit const
qualifier on its operator()
overload.
std::forward
is a conditional move: it is equivalent to std::move
when the provided template argument is not an lvalue reference. It is defined as follows:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(See: https://en.cppreference.com/w/cpp/utility/forward).
Let's simplify your snippet to:
#include <utility>
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[=] func(std::forward<Args>(args)...); ;
int main()
enqueue([](int) , 10);
The error produced by clang++ 8.x
is:
error: no matching function for call to 'forward'
[=] func(std::forward<Args>(args)...); ;
^~~~~~~~~~~~~~~~~~
note: in instantiation of function template specialization 'enqueue<(lambda at wtf.cpp:11:13), int>' requested here
enqueue([](int) , 10);
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
^
In the snippet above:
Args
isint
and refers to the type outside of the lambda.args
refers to the member of the closure synthesized via lambda capture, and isconst
due to the lack ofmutable
.
Therefore the std::forward
invocation is...
std::forward<int>(/* `const int&` member of closure */)
...which doesn't match any existing overload of std::forward
. There is a mismatch between the template argument provided to forward
and its function argument type.
Adding mutable
to the lambda makes args
non-const
, and a suitable forward
overload is found (the first one, which moves its argument).
By using C++20 pack-expansion captures to "rewrite" the name of args
, we can avoid the mismatch mentioned above, making the code compile even without mutable
:
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[func, ...xs = args] func(std::forward<decltype(xs)>(xs)...); ;
live example on godbolt.org
Why is
mutable
required in case ofint double
and not in case ofstring
? What is the difference between these two ?
This is a fun one - it works because you're not actually passing a std::string
in your invocation:
enqueue(f3, "Hello");
// ^~~~~~~
// const char*
If you correctly match the type of the argument passed to enqueue
to the one accepted by f3
, it will stop working as expected (unless you use mutable
or C++20 features):
enqueue(f3, std::string"Hello");
// Compile-time error.
To explain why the version with const char*
works, let's again look at a simplified example:
template <typename T>
void enqueue(T&& func, const char (&arg)[6])
[=] func(std::forward<const char*>(arg)); ;
int main()
enqueue([](std::string) , "Hello");
Args
is deduced as const char(&)[6]
. There is a matching forward
overload:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
After substitution:
template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
This simply returns t
, which is then used to construct the std::string
.
2
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
2
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
2
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
1
Where doesconst char*
come from? He's passing around a reference soArgs
isconst char(&)[6]
so it'sstd::forward<const char(&)[6]>(arg)
.
– 0x499602D2
May 5 at 17:15
|
show 2 more comments
A non-mutable
lambda generates a closure type with an implicit const
qualifier on its operator()
overload.
std::forward
is a conditional move: it is equivalent to std::move
when the provided template argument is not an lvalue reference. It is defined as follows:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(See: https://en.cppreference.com/w/cpp/utility/forward).
Let's simplify your snippet to:
#include <utility>
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[=] func(std::forward<Args>(args)...); ;
int main()
enqueue([](int) , 10);
The error produced by clang++ 8.x
is:
error: no matching function for call to 'forward'
[=] func(std::forward<Args>(args)...); ;
^~~~~~~~~~~~~~~~~~
note: in instantiation of function template specialization 'enqueue<(lambda at wtf.cpp:11:13), int>' requested here
enqueue([](int) , 10);
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
^
In the snippet above:
Args
isint
and refers to the type outside of the lambda.args
refers to the member of the closure synthesized via lambda capture, and isconst
due to the lack ofmutable
.
Therefore the std::forward
invocation is...
std::forward<int>(/* `const int&` member of closure */)
...which doesn't match any existing overload of std::forward
. There is a mismatch between the template argument provided to forward
and its function argument type.
Adding mutable
to the lambda makes args
non-const
, and a suitable forward
overload is found (the first one, which moves its argument).
By using C++20 pack-expansion captures to "rewrite" the name of args
, we can avoid the mismatch mentioned above, making the code compile even without mutable
:
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[func, ...xs = args] func(std::forward<decltype(xs)>(xs)...); ;
live example on godbolt.org
Why is
mutable
required in case ofint double
and not in case ofstring
? What is the difference between these two ?
This is a fun one - it works because you're not actually passing a std::string
in your invocation:
enqueue(f3, "Hello");
// ^~~~~~~
// const char*
If you correctly match the type of the argument passed to enqueue
to the one accepted by f3
, it will stop working as expected (unless you use mutable
or C++20 features):
enqueue(f3, std::string"Hello");
// Compile-time error.
To explain why the version with const char*
works, let's again look at a simplified example:
template <typename T>
void enqueue(T&& func, const char (&arg)[6])
[=] func(std::forward<const char*>(arg)); ;
int main()
enqueue([](std::string) , "Hello");
Args
is deduced as const char(&)[6]
. There is a matching forward
overload:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
After substitution:
template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
This simply returns t
, which is then used to construct the std::string
.
A non-mutable
lambda generates a closure type with an implicit const
qualifier on its operator()
overload.
std::forward
is a conditional move: it is equivalent to std::move
when the provided template argument is not an lvalue reference. It is defined as follows:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t ) noexcept;
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
(See: https://en.cppreference.com/w/cpp/utility/forward).
Let's simplify your snippet to:
#include <utility>
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[=] func(std::forward<Args>(args)...); ;
int main()
enqueue([](int) , 10);
The error produced by clang++ 8.x
is:
error: no matching function for call to 'forward'
[=] func(std::forward<Args>(args)...); ;
^~~~~~~~~~~~~~~~~~
note: in instantiation of function template specialization 'enqueue<(lambda at wtf.cpp:11:13), int>' requested here
enqueue([](int) , 10);
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
^
note: candidate function template not viable: 1st argument ('const int')
would lose const qualifier
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
^
In the snippet above:
Args
isint
and refers to the type outside of the lambda.args
refers to the member of the closure synthesized via lambda capture, and isconst
due to the lack ofmutable
.
Therefore the std::forward
invocation is...
std::forward<int>(/* `const int&` member of closure */)
...which doesn't match any existing overload of std::forward
. There is a mismatch between the template argument provided to forward
and its function argument type.
Adding mutable
to the lambda makes args
non-const
, and a suitable forward
overload is found (the first one, which moves its argument).
By using C++20 pack-expansion captures to "rewrite" the name of args
, we can avoid the mismatch mentioned above, making the code compile even without mutable
:
template <typename T, typename... Args>
void enqueue(T&& func, Args&&... args)
[func, ...xs = args] func(std::forward<decltype(xs)>(xs)...); ;
live example on godbolt.org
Why is
mutable
required in case ofint double
and not in case ofstring
? What is the difference between these two ?
This is a fun one - it works because you're not actually passing a std::string
in your invocation:
enqueue(f3, "Hello");
// ^~~~~~~
// const char*
If you correctly match the type of the argument passed to enqueue
to the one accepted by f3
, it will stop working as expected (unless you use mutable
or C++20 features):
enqueue(f3, std::string"Hello");
// Compile-time error.
To explain why the version with const char*
works, let's again look at a simplified example:
template <typename T>
void enqueue(T&& func, const char (&arg)[6])
[=] func(std::forward<const char*>(arg)); ;
int main()
enqueue([](std::string) , "Hello");
Args
is deduced as const char(&)[6]
. There is a matching forward
overload:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;
After substitution:
template< class T >
constexpr const char*&& forward( const char*&& t ) noexcept;
This simply returns t
, which is then used to construct the std::string
.
edited May 5 at 15:14
answered May 5 at 14:32
Vittorio RomeoVittorio Romeo
60.9k17170315
60.9k17170315
2
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
2
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
2
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
1
Where doesconst char*
come from? He's passing around a reference soArgs
isconst char(&)[6]
so it'sstd::forward<const char(&)[6]>(arg)
.
– 0x499602D2
May 5 at 17:15
|
show 2 more comments
2
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
2
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
2
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
1
Where doesconst char*
come from? He's passing around a reference soArgs
isconst char(&)[6]
so it'sstd::forward<const char(&)[6]>(arg)
.
– 0x499602D2
May 5 at 17:15
2
2
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
Wow, that's an excellent answer! Can you perhaps add some references to the stardard?
– andreee
May 5 at 14:35
2
2
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
@andreee: improved my answer, let me know if it is clear.
– Vittorio Romeo
May 5 at 14:45
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
Thanks for the explanation. I have a doubt, in case of const char *, we also have a const args.. still it works.. why is that ?
– bornfree
May 5 at 14:59
2
2
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
@bornfree: added an explanation.
– Vittorio Romeo
May 5 at 15:14
1
1
Where does
const char*
come from? He's passing around a reference so Args
is const char(&)[6]
so it's std::forward<const char(&)[6]>(arg)
.– 0x499602D2
May 5 at 17:15
Where does
const char*
come from? He's passing around a reference so Args
is const char(&)[6]
so it's std::forward<const char(&)[6]>(arg)
.– 0x499602D2
May 5 at 17:15
|
show 2 more comments
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55992834%2fwhy-cant-argument-be-forwarded-inside-lambda-without-mutable%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
As an aside, in the second example you are not forwarding
std::string
, you are forwardingconst char*
. It doesn't matter what type the function takes, but what type arguments are passed toenqueue
. My guess is, the argument beingconst
from the start somehow makes a difference, but I'm not sure how.– Igor Tandetnik
May 5 at 14:23
4
And indeed, it does fail if you make it
enqueue(f3, std::string("Hello"));
– Igor Tandetnik
May 5 at 14:24
1
...yes, or
"Hello"s
(using string literals)– andreee
May 5 at 14:25
@IgorTandetnik The direction is right: if you pass a
const
argument, the error disappears. See for example godbolt.org/z/EdY4Mn where I just introduced some empty classFoo
. If you remove the constness ofFoo
, it fails to compile. Still not sure why, though...– andreee
May 5 at 14:29
It may be enlightening (although pointless) to realize that using
std::forward<const Args>
also fixes the compile error.– Vaughn Cato
May 5 at 17:14