Passing functions in C++ Announcing the arrival of Valued Associate #679: Cesar Manara Planned maintenance scheduled April 23, 2019 at 00:00UTC (8:00pm US/Eastern) Data science time! April 2019 and salary with experience The Ask Question Wizard is Live!What are the differences between a pointer variable and a reference variable in C++?How can I profile C++ code running on Linux?The Definitive C++ Book Guide and ListWhat is the effect of extern “C” in C++?What is the “-->” operator in C++?Why do we need virtual functions in C++?What are the basic rules and idioms for operator overloading?Easiest way to convert int to string in C++C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?Why is reading lines from stdin much slower in C++ than Python?
Performance gap between vector<bool> and array
Why is Nikon 1.4g better when Nikon 1.8g is sharper?
What is the difference between globalisation and imperialism?
How to compare two different files line by line in unix?
When a candle burns, why does the top of wick glow if bottom of flame is hottest?
Can anything be seen from the center of the Boötes void? How dark would it be?
Effects on objects due to a brief relocation of massive amounts of mass
What do you call the main part of a joke?
An adverb for when you're not exaggerating
What initially awakened the Balrog?
Why does the remaining Rebel fleet at the end of Rogue One seem dramatically larger than the one in A New Hope?
How to react to hostile behavior from a senior developer?
Is CEO the "profession" with the most psychopaths?
What is "gratricide"?
Why do we need to use the builder design pattern when we can do the same thing with setters?
What is the font for "b" letter?
Sum letters are not two different
Crossing US/Canada Border for less than 24 hours
Why is my ESD wriststrap failing with nitrile gloves on?
Trademark violation for app?
How would a mousetrap for use in space work?
Significance of Cersei's obsession with elephants?
How were pictures turned from film to a big picture in a picture frame before digital scanning?
SF book about people trapped in a series of worlds they imagine
Passing functions in C++
Announcing the arrival of Valued Associate #679: Cesar Manara
Planned maintenance scheduled April 23, 2019 at 00:00UTC (8:00pm US/Eastern)
Data science time! April 2019 and salary with experience
The Ask Question Wizard is Live!What are the differences between a pointer variable and a reference variable in C++?How can I profile C++ code running on Linux?The Definitive C++ Book Guide and ListWhat is the effect of extern “C” in C++?What is the “-->” operator in C++?Why do we need virtual functions in C++?What are the basic rules and idioms for operator overloading?Easiest way to convert int to string in C++C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?Why is reading lines from stdin much slower in C++ than Python?
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
Suppose I want to write a function that calls a nullary function 100 times. Which of these implementations is best and why?
template<typename F>
void call100(F f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(const F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
Or is there a better implementation?
Update regarding 4
struct S
S()
S(const S&) = delete;
void operator()() const
;
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
int main()
const S s;
call100(s);
c++ c++17
add a comment |
Suppose I want to write a function that calls a nullary function 100 times. Which of these implementations is best and why?
template<typename F>
void call100(F f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(const F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
Or is there a better implementation?
Update regarding 4
struct S
S()
S(const S&) = delete;
void operator()() const
;
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
int main()
const S s;
call100(s);
c++ c++17
1
go for the last one.
– Hoodi
Apr 14 at 11:40
6
@Hoodi: Why?...
– Andrew Tomazos
Apr 14 at 11:43
Because in your sample code, you just call f one hundred times and you have not changed F. So, go for the last one.
– Hoodi
Apr 14 at 12:06
9
@Hoodi - You have no way to know iff
has any state that changes by callingf()
. That template accepts general functors.
– StoryTeller
Apr 14 at 12:08
add a comment |
Suppose I want to write a function that calls a nullary function 100 times. Which of these implementations is best and why?
template<typename F>
void call100(F f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(const F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
Or is there a better implementation?
Update regarding 4
struct S
S()
S(const S&) = delete;
void operator()() const
;
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
int main()
const S s;
call100(s);
c++ c++17
Suppose I want to write a function that calls a nullary function 100 times. Which of these implementations is best and why?
template<typename F>
void call100(F f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(const F& f)
for (int i = 0; i < 100; i++)
f();
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
Or is there a better implementation?
Update regarding 4
struct S
S()
S(const S&) = delete;
void operator()() const
;
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; i++)
f();
int main()
const S s;
call100(s);
c++ c++17
c++ c++17
edited Apr 14 at 12:14
Andrew Tomazos
asked Apr 14 at 11:37
Andrew TomazosAndrew Tomazos
35.9k26135236
35.9k26135236
1
go for the last one.
– Hoodi
Apr 14 at 11:40
6
@Hoodi: Why?...
– Andrew Tomazos
Apr 14 at 11:43
Because in your sample code, you just call f one hundred times and you have not changed F. So, go for the last one.
– Hoodi
Apr 14 at 12:06
9
@Hoodi - You have no way to know iff
has any state that changes by callingf()
. That template accepts general functors.
– StoryTeller
Apr 14 at 12:08
add a comment |
1
go for the last one.
– Hoodi
Apr 14 at 11:40
6
@Hoodi: Why?...
– Andrew Tomazos
Apr 14 at 11:43
Because in your sample code, you just call f one hundred times and you have not changed F. So, go for the last one.
– Hoodi
Apr 14 at 12:06
9
@Hoodi - You have no way to know iff
has any state that changes by callingf()
. That template accepts general functors.
– StoryTeller
Apr 14 at 12:08
1
1
go for the last one.
– Hoodi
Apr 14 at 11:40
go for the last one.
– Hoodi
Apr 14 at 11:40
6
6
@Hoodi: Why?...
– Andrew Tomazos
Apr 14 at 11:43
@Hoodi: Why?...
– Andrew Tomazos
Apr 14 at 11:43
Because in your sample code, you just call f one hundred times and you have not changed F. So, go for the last one.
– Hoodi
Apr 14 at 12:06
Because in your sample code, you just call f one hundred times and you have not changed F. So, go for the last one.
– Hoodi
Apr 14 at 12:06
9
9
@Hoodi - You have no way to know if
f
has any state that changes by calling f()
. That template accepts general functors.– StoryTeller
Apr 14 at 12:08
@Hoodi - You have no way to know if
f
has any state that changes by calling f()
. That template accepts general functors.– StoryTeller
Apr 14 at 12:08
add a comment |
3 Answers
3
active
oldest
votes
I would use the first one (pass the callable by value).
If a caller is concerned about the cost of copying the callable, then they can use std::ref(f)
or std::cref(f)
to pass it using reference_wrapper
.
By doing this, you provide the most flexibility to the caller.
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
5
Also note this is how the standard library does it in the algorithmns library (e.g.std::for_each
,std::count_if
)
– Artyer
Apr 14 at 12:18
5
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
add a comment |
The only runtime cost of
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; ++i)
f();
is that it can have more versions (copies of code) if you pass f
in multiple ways. With MSVC or the gold linker with ICF, those copies only cost compile time unless they differ, and if they differ you probably want to keep them.
template<typename F>
void call100(F f)
for (int i = 0; i < 100; ++i)
f();
this one has the advantage of being value semantics; and following the rule of taking values unless you have good reason not to is reasonable. std::ref
/std::cref
let you call it with a persistant reference, and for prvalues c++17 guaranteed elision will prevent a spurious copy.
As a joke you could do:
template<typename F>
void call100(F&& f)
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
but that relies on people having &&
overloads on their operator()
, which nobody does.
add a comment |
I do not think there is a definitive answer:
The first one copies everything you pass in which might be expensive
for capturing lambdas but otherwise provides the most flexibility:Pros
- Const objects allowed
- Mutable objects allowed (copied)
- Copy can be elided (?)
Cons
- Copies everything you give it
- You cannot call it with an existing object such as mutable lambda without copying it in
The second one cannot be used for const objects. On the other hand
it does not copy anything and allows mutable objects:Pros
- Mutable objects allowed
- Copies nothing
Cons
- Does not allow const objects
The third one cannot be used for mutable lambdas so is a slight
modification of the second one.Pros
- Const objects allowed
- Copies nothing
Cons
- Cannot be called with mutable objects
The fourth one cannot be called with const objects unless you copy
them which becomes quite awkward with lambdas. You also cannot use
it with pre-existing mutable lambda object without copying it or
moving from it (losing it in the process) which is similar
limitation to 1.Pros
- Avoids copies explicitely by forcing (requiring) move semanthics if the copy is needed
- Mutable objects allowed.
- Const objects allowed (except for mutable lambdas)
Cons
- Does not allow const mutable lambdas without a copy
- You cannot call it with an existing object such as mutable lambda
And there you have it. There is no silver bullet here and there are different pros & cons to each of these versions. I tend to lean towards the first one being the default but with certain types of capturing lambdas or bigger callables, it might become an issue. And you cannot call the 1) with the mutable object and get an expected result. As mentioned in the other answer some of these can be overcome with std::ref
and other ways of manipulating the actual T
type. In my experience, these tend to be the source of pretty nasty bugs though when T
is then something different than one expects to achieve i.e. mutability of a copy or such.
1
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard theconst
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.
– Resurrection
Apr 14 at 12:15
1
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't calloperator()&&
.
– Yakk - Adam Nevraumont
Apr 14 at 12:50
add a comment |
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%2f55674830%2fpassing-functions-in-c%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
I would use the first one (pass the callable by value).
If a caller is concerned about the cost of copying the callable, then they can use std::ref(f)
or std::cref(f)
to pass it using reference_wrapper
.
By doing this, you provide the most flexibility to the caller.
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
5
Also note this is how the standard library does it in the algorithmns library (e.g.std::for_each
,std::count_if
)
– Artyer
Apr 14 at 12:18
5
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
add a comment |
I would use the first one (pass the callable by value).
If a caller is concerned about the cost of copying the callable, then they can use std::ref(f)
or std::cref(f)
to pass it using reference_wrapper
.
By doing this, you provide the most flexibility to the caller.
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
5
Also note this is how the standard library does it in the algorithmns library (e.g.std::for_each
,std::count_if
)
– Artyer
Apr 14 at 12:18
5
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
add a comment |
I would use the first one (pass the callable by value).
If a caller is concerned about the cost of copying the callable, then they can use std::ref(f)
or std::cref(f)
to pass it using reference_wrapper
.
By doing this, you provide the most flexibility to the caller.
I would use the first one (pass the callable by value).
If a caller is concerned about the cost of copying the callable, then they can use std::ref(f)
or std::cref(f)
to pass it using reference_wrapper
.
By doing this, you provide the most flexibility to the caller.
edited Apr 15 at 3:41
jww
54.5k42240520
54.5k42240520
answered Apr 14 at 11:47
Marshall ClowMarshall Clow
7,7431635
7,7431635
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
5
Also note this is how the standard library does it in the algorithmns library (e.g.std::for_each
,std::count_if
)
– Artyer
Apr 14 at 12:18
5
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
add a comment |
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
5
Also note this is how the standard library does it in the algorithmns library (e.g.std::for_each
,std::count_if
)
– Artyer
Apr 14 at 12:18
5
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
But in the main code of the question, it JUST calls the f 100 times. No?
– Hoodi
Apr 14 at 12:02
5
5
Also note this is how the standard library does it in the algorithmns library (e.g.
std::for_each
, std::count_if
)– Artyer
Apr 14 at 12:18
Also note this is how the standard library does it in the algorithmns library (e.g.
std::for_each
, std::count_if
)– Artyer
Apr 14 at 12:18
5
5
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
@Artyer Although those predate forwarding references.
– Barry
Apr 14 at 17:32
add a comment |
The only runtime cost of
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; ++i)
f();
is that it can have more versions (copies of code) if you pass f
in multiple ways. With MSVC or the gold linker with ICF, those copies only cost compile time unless they differ, and if they differ you probably want to keep them.
template<typename F>
void call100(F f)
for (int i = 0; i < 100; ++i)
f();
this one has the advantage of being value semantics; and following the rule of taking values unless you have good reason not to is reasonable. std::ref
/std::cref
let you call it with a persistant reference, and for prvalues c++17 guaranteed elision will prevent a spurious copy.
As a joke you could do:
template<typename F>
void call100(F&& f)
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
but that relies on people having &&
overloads on their operator()
, which nobody does.
add a comment |
The only runtime cost of
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; ++i)
f();
is that it can have more versions (copies of code) if you pass f
in multiple ways. With MSVC or the gold linker with ICF, those copies only cost compile time unless they differ, and if they differ you probably want to keep them.
template<typename F>
void call100(F f)
for (int i = 0; i < 100; ++i)
f();
this one has the advantage of being value semantics; and following the rule of taking values unless you have good reason not to is reasonable. std::ref
/std::cref
let you call it with a persistant reference, and for prvalues c++17 guaranteed elision will prevent a spurious copy.
As a joke you could do:
template<typename F>
void call100(F&& f)
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
but that relies on people having &&
overloads on their operator()
, which nobody does.
add a comment |
The only runtime cost of
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; ++i)
f();
is that it can have more versions (copies of code) if you pass f
in multiple ways. With MSVC or the gold linker with ICF, those copies only cost compile time unless they differ, and if they differ you probably want to keep them.
template<typename F>
void call100(F f)
for (int i = 0; i < 100; ++i)
f();
this one has the advantage of being value semantics; and following the rule of taking values unless you have good reason not to is reasonable. std::ref
/std::cref
let you call it with a persistant reference, and for prvalues c++17 guaranteed elision will prevent a spurious copy.
As a joke you could do:
template<typename F>
void call100(F&& f)
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
but that relies on people having &&
overloads on their operator()
, which nobody does.
The only runtime cost of
template<typename F>
void call100(F&& f)
for (int i = 0; i < 100; ++i)
f();
is that it can have more versions (copies of code) if you pass f
in multiple ways. With MSVC or the gold linker with ICF, those copies only cost compile time unless they differ, and if they differ you probably want to keep them.
template<typename F>
void call100(F f)
for (int i = 0; i < 100; ++i)
f();
this one has the advantage of being value semantics; and following the rule of taking values unless you have good reason not to is reasonable. std::ref
/std::cref
let you call it with a persistant reference, and for prvalues c++17 guaranteed elision will prevent a spurious copy.
As a joke you could do:
template<typename F>
void call100(F&& f)
for (int i = 0; i < 99; ++i)
f();
std::forward<F>(f)();
but that relies on people having &&
overloads on their operator()
, which nobody does.
edited Apr 14 at 14:21
answered Apr 14 at 12:49
Yakk - Adam NevraumontYakk - Adam Nevraumont
190k21200385
190k21200385
add a comment |
add a comment |
I do not think there is a definitive answer:
The first one copies everything you pass in which might be expensive
for capturing lambdas but otherwise provides the most flexibility:Pros
- Const objects allowed
- Mutable objects allowed (copied)
- Copy can be elided (?)
Cons
- Copies everything you give it
- You cannot call it with an existing object such as mutable lambda without copying it in
The second one cannot be used for const objects. On the other hand
it does not copy anything and allows mutable objects:Pros
- Mutable objects allowed
- Copies nothing
Cons
- Does not allow const objects
The third one cannot be used for mutable lambdas so is a slight
modification of the second one.Pros
- Const objects allowed
- Copies nothing
Cons
- Cannot be called with mutable objects
The fourth one cannot be called with const objects unless you copy
them which becomes quite awkward with lambdas. You also cannot use
it with pre-existing mutable lambda object without copying it or
moving from it (losing it in the process) which is similar
limitation to 1.Pros
- Avoids copies explicitely by forcing (requiring) move semanthics if the copy is needed
- Mutable objects allowed.
- Const objects allowed (except for mutable lambdas)
Cons
- Does not allow const mutable lambdas without a copy
- You cannot call it with an existing object such as mutable lambda
And there you have it. There is no silver bullet here and there are different pros & cons to each of these versions. I tend to lean towards the first one being the default but with certain types of capturing lambdas or bigger callables, it might become an issue. And you cannot call the 1) with the mutable object and get an expected result. As mentioned in the other answer some of these can be overcome with std::ref
and other ways of manipulating the actual T
type. In my experience, these tend to be the source of pretty nasty bugs though when T
is then something different than one expects to achieve i.e. mutability of a copy or such.
1
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard theconst
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.
– Resurrection
Apr 14 at 12:15
1
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't calloperator()&&
.
– Yakk - Adam Nevraumont
Apr 14 at 12:50
add a comment |
I do not think there is a definitive answer:
The first one copies everything you pass in which might be expensive
for capturing lambdas but otherwise provides the most flexibility:Pros
- Const objects allowed
- Mutable objects allowed (copied)
- Copy can be elided (?)
Cons
- Copies everything you give it
- You cannot call it with an existing object such as mutable lambda without copying it in
The second one cannot be used for const objects. On the other hand
it does not copy anything and allows mutable objects:Pros
- Mutable objects allowed
- Copies nothing
Cons
- Does not allow const objects
The third one cannot be used for mutable lambdas so is a slight
modification of the second one.Pros
- Const objects allowed
- Copies nothing
Cons
- Cannot be called with mutable objects
The fourth one cannot be called with const objects unless you copy
them which becomes quite awkward with lambdas. You also cannot use
it with pre-existing mutable lambda object without copying it or
moving from it (losing it in the process) which is similar
limitation to 1.Pros
- Avoids copies explicitely by forcing (requiring) move semanthics if the copy is needed
- Mutable objects allowed.
- Const objects allowed (except for mutable lambdas)
Cons
- Does not allow const mutable lambdas without a copy
- You cannot call it with an existing object such as mutable lambda
And there you have it. There is no silver bullet here and there are different pros & cons to each of these versions. I tend to lean towards the first one being the default but with certain types of capturing lambdas or bigger callables, it might become an issue. And you cannot call the 1) with the mutable object and get an expected result. As mentioned in the other answer some of these can be overcome with std::ref
and other ways of manipulating the actual T
type. In my experience, these tend to be the source of pretty nasty bugs though when T
is then something different than one expects to achieve i.e. mutability of a copy or such.
1
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard theconst
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.
– Resurrection
Apr 14 at 12:15
1
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't calloperator()&&
.
– Yakk - Adam Nevraumont
Apr 14 at 12:50
add a comment |
I do not think there is a definitive answer:
The first one copies everything you pass in which might be expensive
for capturing lambdas but otherwise provides the most flexibility:Pros
- Const objects allowed
- Mutable objects allowed (copied)
- Copy can be elided (?)
Cons
- Copies everything you give it
- You cannot call it with an existing object such as mutable lambda without copying it in
The second one cannot be used for const objects. On the other hand
it does not copy anything and allows mutable objects:Pros
- Mutable objects allowed
- Copies nothing
Cons
- Does not allow const objects
The third one cannot be used for mutable lambdas so is a slight
modification of the second one.Pros
- Const objects allowed
- Copies nothing
Cons
- Cannot be called with mutable objects
The fourth one cannot be called with const objects unless you copy
them which becomes quite awkward with lambdas. You also cannot use
it with pre-existing mutable lambda object without copying it or
moving from it (losing it in the process) which is similar
limitation to 1.Pros
- Avoids copies explicitely by forcing (requiring) move semanthics if the copy is needed
- Mutable objects allowed.
- Const objects allowed (except for mutable lambdas)
Cons
- Does not allow const mutable lambdas without a copy
- You cannot call it with an existing object such as mutable lambda
And there you have it. There is no silver bullet here and there are different pros & cons to each of these versions. I tend to lean towards the first one being the default but with certain types of capturing lambdas or bigger callables, it might become an issue. And you cannot call the 1) with the mutable object and get an expected result. As mentioned in the other answer some of these can be overcome with std::ref
and other ways of manipulating the actual T
type. In my experience, these tend to be the source of pretty nasty bugs though when T
is then something different than one expects to achieve i.e. mutability of a copy or such.
I do not think there is a definitive answer:
The first one copies everything you pass in which might be expensive
for capturing lambdas but otherwise provides the most flexibility:Pros
- Const objects allowed
- Mutable objects allowed (copied)
- Copy can be elided (?)
Cons
- Copies everything you give it
- You cannot call it with an existing object such as mutable lambda without copying it in
The second one cannot be used for const objects. On the other hand
it does not copy anything and allows mutable objects:Pros
- Mutable objects allowed
- Copies nothing
Cons
- Does not allow const objects
The third one cannot be used for mutable lambdas so is a slight
modification of the second one.Pros
- Const objects allowed
- Copies nothing
Cons
- Cannot be called with mutable objects
The fourth one cannot be called with const objects unless you copy
them which becomes quite awkward with lambdas. You also cannot use
it with pre-existing mutable lambda object without copying it or
moving from it (losing it in the process) which is similar
limitation to 1.Pros
- Avoids copies explicitely by forcing (requiring) move semanthics if the copy is needed
- Mutable objects allowed.
- Const objects allowed (except for mutable lambdas)
Cons
- Does not allow const mutable lambdas without a copy
- You cannot call it with an existing object such as mutable lambda
And there you have it. There is no silver bullet here and there are different pros & cons to each of these versions. I tend to lean towards the first one being the default but with certain types of capturing lambdas or bigger callables, it might become an issue. And you cannot call the 1) with the mutable object and get an expected result. As mentioned in the other answer some of these can be overcome with std::ref
and other ways of manipulating the actual T
type. In my experience, these tend to be the source of pretty nasty bugs though when T
is then something different than one expects to achieve i.e. mutability of a copy or such.
edited Apr 14 at 15:38
JeJo
4,5573926
4,5573926
answered Apr 14 at 12:06
ResurrectionResurrection
2,13411839
2,13411839
1
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard theconst
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.
– Resurrection
Apr 14 at 12:15
1
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't calloperator()&&
.
– Yakk - Adam Nevraumont
Apr 14 at 12:50
add a comment |
1
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard theconst
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.
– Resurrection
Apr 14 at 12:15
1
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't calloperator()&&
.
– Yakk - Adam Nevraumont
Apr 14 at 12:50
1
1
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
I don't think your analysis of 4 is correct. Passed a const value won't F be deduced as const T& and be passed by reference?
– Andrew Tomazos
Apr 14 at 12:12
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
See "Update regarding 4"
– Andrew Tomazos
Apr 14 at 12:14
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard the
const
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.– Resurrection
Apr 14 at 12:15
@AndrewTomazos Not with const mutable lambdas. Or rather it will deduce it as you say but would refuse to compile because it would discard the
const
at the call site. Using latest MSVC2017, not sure about Clang/GCC as Godbolt seems not to work atm.– Resurrection
Apr 14 at 12:15
1
1
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't call
operator()&&
.– Yakk - Adam Nevraumont
Apr 14 at 12:50
@artyr No, that is nonsense. Feel free to test it yourself, but 4 won't call
operator()&&
.– Yakk - Adam Nevraumont
Apr 14 at 12:50
add a comment |
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%2f55674830%2fpassing-functions-in-c%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
1
go for the last one.
– Hoodi
Apr 14 at 11:40
6
@Hoodi: Why?...
– Andrew Tomazos
Apr 14 at 11:43
Because in your sample code, you just call f one hundred times and you have not changed F. So, go for the last one.
– Hoodi
Apr 14 at 12:06
9
@Hoodi - You have no way to know if
f
has any state that changes by callingf()
. That template accepts general functors.– StoryTeller
Apr 14 at 12:08