Function argument returning void or non-void typeDoes the 'mutable' keyword have any purpose other than allowing the variable to be modified by a const function?Returning a const reference to an object instead of a copyWhat are POD types in C++?Why do we need virtual functions in C++?Pretty-print C++ STL containersReturn type deduction: What method is preferred?How do I declare a function whose return type is deduced?Template friend function and return type deductionvoid troubles return value to stringVariadic function wrapper for any return type
Inward extrusion is not working
Geopandas and QGIS Calulating Different Polygon Area Values?
Meaning of 'lose their grip on the groins of their followers'
Should I give professor gift at the beginning of my PhD?
Zeros of the Hadamard product of holomorphic functions
Is it possible to have a wealthy country without a middle class?
Is White controlling this game?
You have (3^2 + 2^3 + 2^2) Guesses Left. Figure out the Last one
Why doesn't Adrian Toomes give up Spider-Man's identity?
Someone whose aspirations exceed abilities or means
Soft question: Examples where lack of mathematical rigour cause security breaches?
Winning Strategy for the Magician and his Apprentice
How to hide an urban landmark?
SQL counting distinct over partition
Overlapping String-Blocks
Compiling C files on Ubuntu and using the executable on Windows
Check if three arrays contains the same element
How to tell your grandparent to not come to fetch you with their car?
Were Alexander the Great and Hephaestion lovers?
Are there any important biographies of nobodies?
Does Disney no longer produce hand-drawn cartoon films?
How is water heavier than petrol, even though its molecular weight is less than petrol?
Determining fair price for profitable mobile app business
Jargon request: "Canonical Form" of a word
Function argument returning void or non-void type
Does the 'mutable' keyword have any purpose other than allowing the variable to be modified by a const function?Returning a const reference to an object instead of a copyWhat are POD types in C++?Why do we need virtual functions in C++?Pretty-print C++ STL containersReturn type deduction: What method is preferred?How do I declare a function whose return type is deduced?Template friend function and return type deductionvoid troubles return value to stringVariadic function wrapper for any return type
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
I am in the middle of writing some generic code for a future library. I came across the following problem inside a template function. Consider the code below:
template<class F>
auto foo(F &&f)
auto result = std::forward<F>(f)(/*some args*/);
//do some generic stuff
return result;
It will work fine, unless I pass to it a function that returns void
like:
foo([]());
Now, of course, I could use some std::enable_if
magic to check the return type and perform specialization for a function returning void
that looks like this:
template<class F, class = /*enable if stuff*/>
void foo(F &&f)
std::forward<F>(f)(/*some args*/);
//do some generic stuff
But that would awfully duplicate code for actually logically equivalent functions. Can this be done easily in a generic way for both void
-returning and non-void
-returning functions in a elegant way?
EDIT:
there is data dependency between function f()
and generic stuff I want to do, so I do not take code like this into account:
template<class F>
auto foo(F &&f)
//do some generic stuff
return std::forward<F>(f)(/*some args*/);
c++ templates c++14
add a comment |
I am in the middle of writing some generic code for a future library. I came across the following problem inside a template function. Consider the code below:
template<class F>
auto foo(F &&f)
auto result = std::forward<F>(f)(/*some args*/);
//do some generic stuff
return result;
It will work fine, unless I pass to it a function that returns void
like:
foo([]());
Now, of course, I could use some std::enable_if
magic to check the return type and perform specialization for a function returning void
that looks like this:
template<class F, class = /*enable if stuff*/>
void foo(F &&f)
std::forward<F>(f)(/*some args*/);
//do some generic stuff
But that would awfully duplicate code for actually logically equivalent functions. Can this be done easily in a generic way for both void
-returning and non-void
-returning functions in a elegant way?
EDIT:
there is data dependency between function f()
and generic stuff I want to do, so I do not take code like this into account:
template<class F>
auto foo(F &&f)
//do some generic stuff
return std::forward<F>(f)(/*some args*/);
c++ templates c++14
4
offtopic: thisstd::forward
is misused. It should be used only if argument can be moved to next function/template. It doesn't break anything here it is just boilerplate.
– Marek R
May 22 at 12:31
After your edit: How can this possibly work withvoid
return types? Either you assign the return value toresult
or you leave it.auto return = some_void_func()
doesn't make any sense.
– andreee
May 22 at 12:31
4
@MarekR what if function object with rvalue only overload ofoperator()
is passed? Will it work withoutforward
?
– bartop
May 22 at 12:35
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, sostd::forward
can have sense here.
– Marek R
May 22 at 12:44
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
May 22 at 13:19
add a comment |
I am in the middle of writing some generic code for a future library. I came across the following problem inside a template function. Consider the code below:
template<class F>
auto foo(F &&f)
auto result = std::forward<F>(f)(/*some args*/);
//do some generic stuff
return result;
It will work fine, unless I pass to it a function that returns void
like:
foo([]());
Now, of course, I could use some std::enable_if
magic to check the return type and perform specialization for a function returning void
that looks like this:
template<class F, class = /*enable if stuff*/>
void foo(F &&f)
std::forward<F>(f)(/*some args*/);
//do some generic stuff
But that would awfully duplicate code for actually logically equivalent functions. Can this be done easily in a generic way for both void
-returning and non-void
-returning functions in a elegant way?
EDIT:
there is data dependency between function f()
and generic stuff I want to do, so I do not take code like this into account:
template<class F>
auto foo(F &&f)
//do some generic stuff
return std::forward<F>(f)(/*some args*/);
c++ templates c++14
I am in the middle of writing some generic code for a future library. I came across the following problem inside a template function. Consider the code below:
template<class F>
auto foo(F &&f)
auto result = std::forward<F>(f)(/*some args*/);
//do some generic stuff
return result;
It will work fine, unless I pass to it a function that returns void
like:
foo([]());
Now, of course, I could use some std::enable_if
magic to check the return type and perform specialization for a function returning void
that looks like this:
template<class F, class = /*enable if stuff*/>
void foo(F &&f)
std::forward<F>(f)(/*some args*/);
//do some generic stuff
But that would awfully duplicate code for actually logically equivalent functions. Can this be done easily in a generic way for both void
-returning and non-void
-returning functions in a elegant way?
EDIT:
there is data dependency between function f()
and generic stuff I want to do, so I do not take code like this into account:
template<class F>
auto foo(F &&f)
//do some generic stuff
return std::forward<F>(f)(/*some args*/);
c++ templates c++14
c++ templates c++14
edited May 23 at 1:44
Marc.2377
2,32632356
2,32632356
asked May 22 at 12:18
bartopbartop
3,6821135
3,6821135
4
offtopic: thisstd::forward
is misused. It should be used only if argument can be moved to next function/template. It doesn't break anything here it is just boilerplate.
– Marek R
May 22 at 12:31
After your edit: How can this possibly work withvoid
return types? Either you assign the return value toresult
or you leave it.auto return = some_void_func()
doesn't make any sense.
– andreee
May 22 at 12:31
4
@MarekR what if function object with rvalue only overload ofoperator()
is passed? Will it work withoutforward
?
– bartop
May 22 at 12:35
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, sostd::forward
can have sense here.
– Marek R
May 22 at 12:44
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
May 22 at 13:19
add a comment |
4
offtopic: thisstd::forward
is misused. It should be used only if argument can be moved to next function/template. It doesn't break anything here it is just boilerplate.
– Marek R
May 22 at 12:31
After your edit: How can this possibly work withvoid
return types? Either you assign the return value toresult
or you leave it.auto return = some_void_func()
doesn't make any sense.
– andreee
May 22 at 12:31
4
@MarekR what if function object with rvalue only overload ofoperator()
is passed? Will it work withoutforward
?
– bartop
May 22 at 12:35
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, sostd::forward
can have sense here.
– Marek R
May 22 at 12:44
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
May 22 at 13:19
4
4
offtopic: this
std::forward
is misused. It should be used only if argument can be moved to next function/template. It doesn't break anything here it is just boilerplate.– Marek R
May 22 at 12:31
offtopic: this
std::forward
is misused. It should be used only if argument can be moved to next function/template. It doesn't break anything here it is just boilerplate.– Marek R
May 22 at 12:31
After your edit: How can this possibly work with
void
return types? Either you assign the return value to result
or you leave it. auto return = some_void_func()
doesn't make any sense.– andreee
May 22 at 12:31
After your edit: How can this possibly work with
void
return types? Either you assign the return value to result
or you leave it. auto return = some_void_func()
doesn't make any sense.– andreee
May 22 at 12:31
4
4
@MarekR what if function object with rvalue only overload of
operator()
is passed? Will it work without forward
?– bartop
May 22 at 12:35
@MarekR what if function object with rvalue only overload of
operator()
is passed? Will it work without forward
?– bartop
May 22 at 12:35
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, so
std::forward
can have sense here.– Marek R
May 22 at 12:44
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, so
std::forward
can have sense here.– Marek R
May 22 at 12:44
1
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
May 22 at 13:19
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
May 22 at 13:19
add a comment |
4 Answers
4
active
oldest
votes
if you can place the "some generic stuff" in the destructor of a bar
class (inside a security try/catch block, if you're not sure that doesn't throw exceptions, as pointed by Drax), you can simply write
template <typename F>
auto foo (F &&f)
bar b;
return std::forward<F>(f)(/*some args*/);
So the compiler compute f(/*some args*/)
, exec the destructor of b
and return the computed value (or nothing).
Observe that return func();
, where func()
is a function returning void
, is perfectly legal.
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
1
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
add a comment |
Some specialization, somewhere, is necessary. But the goal here is to avoid specializing the function itself. However, you can specialize a helper class.
Tested with gcc 9.1 with -std=c++17
.
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: valf(std::forward<Args>(args)...)
T value() const
return val;
;
template<>
struct return_value<void>
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
f(std::forward<Args>(args)...);
void value() const
;
template<class F>
auto foo(F &&f)
return_value<decltype(std::declval<F &&>()(2, 4))> rf, 2, 4;
// Something
return r.value();
int main()
foo( [](int a, int b) return; );
std::cout << foo( [](int a, int b) return a+b; ) << std::endl;
"Some specialization, somewhere, is necessary." not with aFinally
raii code, as proposed in another answer.
– Jarod42
May 22 at 13:03
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed byf()
(if notvoid
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.
– max66
May 22 at 13:13
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
An extra copy could be avoided by havingfoo()
return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
May 22 at 14:29
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
add a comment |
The best way to do this, in my opinion, is to actually change the way you call your possibly-void-returning functions. Basically, we change the ones that return void
to instead return some class type Void
that is, for all intents and purposes, the same thing and no users really are going to care.
struct Void ;
All we need to do is to wrap the invocation. The following uses C++17 names (std::invoke
and std::invoke_result_t
) but they're all implementable in C++14 without too much fuss:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args)
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args)
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void;
The advantage of doing it this way is that you can just directly write the code you wanted to write to begin with, in the way you wanted to write it:
template<class F>
auto foo(F &&f)
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
And you don't have to either shove all your logic in a destructor or duplicate all of your logic by doing specialization. At the cost of foo([])
returning Void
instead of void
, which isn't much of a cost.
And then if Regular Void is ever adopted, all you have to do is swap out invoke_void
for std::invoke
.
add a comment |
In case you need to use result
(in non-void cases) in "some generic stuff", I propose a if constexpr
based solution (so, unfortunately, not before C++17).
Not really elegant, to be honest.
First of all, detect the "true return type" of f
(given the arguments)
using TR_t = std::invoke_result_t<F, As...>;
Next a constexpr
variable to see if the returned type is void
(just to simplify a little the following code)
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
Now we define a (potentially) "fake return type": int
when the true return type is void
, the TR_t
otherwise
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
Then we define the a smart pointer to the result value as pointer to the "fake return type" (so int
in void case)
std::unique_ptr<FR_t> pResult;
Passing through a pointer, instead of a simple variable of type "fake return type", we can operate also when TR_t
isn't default constructible or not assignable (limits, pointed by Barry (thanks), of the first version of this answer).
Now, using if constexpr
, the two case to exec f
(this, IMHO, is the ugliest part because we have to write two times the same f
invocation)
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
After this, the "some generic stuff" that can use result
(in non-void cases) and also `isVoidTR).
To conclude, another if constexpr
if constexpr ( isVoidTR )
return;
else
return *pResult;
As pointed by Barry, this solution has some important downsides because (not void
cases)
- require an allocation
- require an extra copy in correspondence of the
return
- doesn't works at all if the
TR_t
(the type returned byf()
) is a reference type
Anyway, the following is a full compiling C++17 example
#include <memory>
#include <type_traits>
template <typename F, typename ... As>
auto foo (F && f, As && ... args)
// true return type
using TR_t = std::invoke_result_t<F, As...>;
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
// (possibly) fake return type
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
std::unique_ptr<FR_t> pResult;
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return *pResult;
int main ()
foo([]());
//auto a foo([]()) ; // compilation error: foo() is void
auto b foo([](auto a0, auto...) return a0; , 1, 2l, 3ll) ;
static_assert( std::is_same_v<decltype(b), int> );
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping outvoid
for a regular type, is a good one though - see my answer for a better way to implement that idea.
– Barry
May 23 at 2:45
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (foo()
never returnvoid
anymore).
– max66
May 23 at 10:26
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
|
show 16 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%2f56256640%2ffunction-argument-returning-void-or-non-void-type%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
if you can place the "some generic stuff" in the destructor of a bar
class (inside a security try/catch block, if you're not sure that doesn't throw exceptions, as pointed by Drax), you can simply write
template <typename F>
auto foo (F &&f)
bar b;
return std::forward<F>(f)(/*some args*/);
So the compiler compute f(/*some args*/)
, exec the destructor of b
and return the computed value (or nothing).
Observe that return func();
, where func()
is a function returning void
, is perfectly legal.
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
1
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
add a comment |
if you can place the "some generic stuff" in the destructor of a bar
class (inside a security try/catch block, if you're not sure that doesn't throw exceptions, as pointed by Drax), you can simply write
template <typename F>
auto foo (F &&f)
bar b;
return std::forward<F>(f)(/*some args*/);
So the compiler compute f(/*some args*/)
, exec the destructor of b
and return the computed value (or nothing).
Observe that return func();
, where func()
is a function returning void
, is perfectly legal.
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
1
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
add a comment |
if you can place the "some generic stuff" in the destructor of a bar
class (inside a security try/catch block, if you're not sure that doesn't throw exceptions, as pointed by Drax), you can simply write
template <typename F>
auto foo (F &&f)
bar b;
return std::forward<F>(f)(/*some args*/);
So the compiler compute f(/*some args*/)
, exec the destructor of b
and return the computed value (or nothing).
Observe that return func();
, where func()
is a function returning void
, is perfectly legal.
if you can place the "some generic stuff" in the destructor of a bar
class (inside a security try/catch block, if you're not sure that doesn't throw exceptions, as pointed by Drax), you can simply write
template <typename F>
auto foo (F &&f)
bar b;
return std::forward<F>(f)(/*some args*/);
So the compiler compute f(/*some args*/)
, exec the destructor of b
and return the computed value (or nothing).
Observe that return func();
, where func()
is a function returning void
, is perfectly legal.
edited May 22 at 13:07
answered May 22 at 12:21
max66max66
41.9k74777
41.9k74777
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
1
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
add a comment |
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
1
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
2
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
bonus tip: the "generic stuff" should better not throw anything
– Drax
May 22 at 12:43
1
1
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
@Drax - good point; thanks; added in the answer.
– max66
May 22 at 12:47
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
And what if it does throw something? What if you need to propagate that exception?
– Barry
May 23 at 12:20
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
@Barry - yes: this is another limit of this solution.
– max66
May 23 at 12:27
add a comment |
Some specialization, somewhere, is necessary. But the goal here is to avoid specializing the function itself. However, you can specialize a helper class.
Tested with gcc 9.1 with -std=c++17
.
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: valf(std::forward<Args>(args)...)
T value() const
return val;
;
template<>
struct return_value<void>
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
f(std::forward<Args>(args)...);
void value() const
;
template<class F>
auto foo(F &&f)
return_value<decltype(std::declval<F &&>()(2, 4))> rf, 2, 4;
// Something
return r.value();
int main()
foo( [](int a, int b) return; );
std::cout << foo( [](int a, int b) return a+b; ) << std::endl;
"Some specialization, somewhere, is necessary." not with aFinally
raii code, as proposed in another answer.
– Jarod42
May 22 at 13:03
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed byf()
(if notvoid
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.
– max66
May 22 at 13:13
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
An extra copy could be avoided by havingfoo()
return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
May 22 at 14:29
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
add a comment |
Some specialization, somewhere, is necessary. But the goal here is to avoid specializing the function itself. However, you can specialize a helper class.
Tested with gcc 9.1 with -std=c++17
.
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: valf(std::forward<Args>(args)...)
T value() const
return val;
;
template<>
struct return_value<void>
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
f(std::forward<Args>(args)...);
void value() const
;
template<class F>
auto foo(F &&f)
return_value<decltype(std::declval<F &&>()(2, 4))> rf, 2, 4;
// Something
return r.value();
int main()
foo( [](int a, int b) return; );
std::cout << foo( [](int a, int b) return a+b; ) << std::endl;
"Some specialization, somewhere, is necessary." not with aFinally
raii code, as proposed in another answer.
– Jarod42
May 22 at 13:03
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed byf()
(if notvoid
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.
– max66
May 22 at 13:13
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
An extra copy could be avoided by havingfoo()
return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
May 22 at 14:29
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
add a comment |
Some specialization, somewhere, is necessary. But the goal here is to avoid specializing the function itself. However, you can specialize a helper class.
Tested with gcc 9.1 with -std=c++17
.
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: valf(std::forward<Args>(args)...)
T value() const
return val;
;
template<>
struct return_value<void>
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
f(std::forward<Args>(args)...);
void value() const
;
template<class F>
auto foo(F &&f)
return_value<decltype(std::declval<F &&>()(2, 4))> rf, 2, 4;
// Something
return r.value();
int main()
foo( [](int a, int b) return; );
std::cout << foo( [](int a, int b) return a+b; ) << std::endl;
Some specialization, somewhere, is necessary. But the goal here is to avoid specializing the function itself. However, you can specialize a helper class.
Tested with gcc 9.1 with -std=c++17
.
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: valf(std::forward<Args>(args)...)
T value() const
return val;
;
template<>
struct return_value<void>
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
f(std::forward<Args>(args)...);
void value() const
;
template<class F>
auto foo(F &&f)
return_value<decltype(std::declval<F &&>()(2, 4))> rf, 2, 4;
// Something
return r.value();
int main()
foo( [](int a, int b) return; );
std::cout << foo( [](int a, int b) return a+b; ) << std::endl;
answered May 22 at 12:36
Sam VarshavchikSam Varshavchik
64.7k53783
64.7k53783
"Some specialization, somewhere, is necessary." not with aFinally
raii code, as proposed in another answer.
– Jarod42
May 22 at 13:03
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed byf()
(if notvoid
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.
– max66
May 22 at 13:13
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
An extra copy could be avoided by havingfoo()
return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
May 22 at 14:29
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
add a comment |
"Some specialization, somewhere, is necessary." not with aFinally
raii code, as proposed in another answer.
– Jarod42
May 22 at 13:03
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed byf()
(if notvoid
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.
– max66
May 22 at 13:13
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
An extra copy could be avoided by havingfoo()
return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
May 22 at 14:29
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
"Some specialization, somewhere, is necessary." not with a
Finally
raii code, as proposed in another answer.– Jarod42
May 22 at 13:03
"Some specialization, somewhere, is necessary." not with a
Finally
raii code, as proposed in another answer.– Jarod42
May 22 at 13:03
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed by
f()
(if not void
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.– max66
May 22 at 13:13
@Jarod42 - but the other answer has a great limit: if the "some generic stuff" needs the value computed by
f()
(if not void
) I don't see a way to avoid a specialization somewhere. I suspect that Sam's answer is better than mine.– max66
May 22 at 13:13
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
This incurs an unconditional extra copy.
– Barry
May 22 at 14:20
An extra copy could be avoided by having
foo()
return the helper object itself. Which will necessitate changing the callers.– Sam Varshavchik
May 22 at 14:29
An extra copy could be avoided by having
foo()
return the helper object itself. Which will necessitate changing the callers.– Sam Varshavchik
May 22 at 14:29
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
@SamVarshavchik That's... not a solution.
– Barry
May 22 at 14:37
add a comment |
The best way to do this, in my opinion, is to actually change the way you call your possibly-void-returning functions. Basically, we change the ones that return void
to instead return some class type Void
that is, for all intents and purposes, the same thing and no users really are going to care.
struct Void ;
All we need to do is to wrap the invocation. The following uses C++17 names (std::invoke
and std::invoke_result_t
) but they're all implementable in C++14 without too much fuss:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args)
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args)
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void;
The advantage of doing it this way is that you can just directly write the code you wanted to write to begin with, in the way you wanted to write it:
template<class F>
auto foo(F &&f)
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
And you don't have to either shove all your logic in a destructor or duplicate all of your logic by doing specialization. At the cost of foo([])
returning Void
instead of void
, which isn't much of a cost.
And then if Regular Void is ever adopted, all you have to do is swap out invoke_void
for std::invoke
.
add a comment |
The best way to do this, in my opinion, is to actually change the way you call your possibly-void-returning functions. Basically, we change the ones that return void
to instead return some class type Void
that is, for all intents and purposes, the same thing and no users really are going to care.
struct Void ;
All we need to do is to wrap the invocation. The following uses C++17 names (std::invoke
and std::invoke_result_t
) but they're all implementable in C++14 without too much fuss:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args)
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args)
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void;
The advantage of doing it this way is that you can just directly write the code you wanted to write to begin with, in the way you wanted to write it:
template<class F>
auto foo(F &&f)
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
And you don't have to either shove all your logic in a destructor or duplicate all of your logic by doing specialization. At the cost of foo([])
returning Void
instead of void
, which isn't much of a cost.
And then if Regular Void is ever adopted, all you have to do is swap out invoke_void
for std::invoke
.
add a comment |
The best way to do this, in my opinion, is to actually change the way you call your possibly-void-returning functions. Basically, we change the ones that return void
to instead return some class type Void
that is, for all intents and purposes, the same thing and no users really are going to care.
struct Void ;
All we need to do is to wrap the invocation. The following uses C++17 names (std::invoke
and std::invoke_result_t
) but they're all implementable in C++14 without too much fuss:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args)
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args)
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void;
The advantage of doing it this way is that you can just directly write the code you wanted to write to begin with, in the way you wanted to write it:
template<class F>
auto foo(F &&f)
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
And you don't have to either shove all your logic in a destructor or duplicate all of your logic by doing specialization. At the cost of foo([])
returning Void
instead of void
, which isn't much of a cost.
And then if Regular Void is ever adopted, all you have to do is swap out invoke_void
for std::invoke
.
The best way to do this, in my opinion, is to actually change the way you call your possibly-void-returning functions. Basically, we change the ones that return void
to instead return some class type Void
that is, for all intents and purposes, the same thing and no users really are going to care.
struct Void ;
All we need to do is to wrap the invocation. The following uses C++17 names (std::invoke
and std::invoke_result_t
) but they're all implementable in C++14 without too much fuss:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args)
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args)
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void;
The advantage of doing it this way is that you can just directly write the code you wanted to write to begin with, in the way you wanted to write it:
template<class F>
auto foo(F &&f)
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
And you don't have to either shove all your logic in a destructor or duplicate all of your logic by doing specialization. At the cost of foo([])
returning Void
instead of void
, which isn't much of a cost.
And then if Regular Void is ever adopted, all you have to do is swap out invoke_void
for std::invoke
.
answered May 22 at 14:12
BarryBarry
192k21350635
192k21350635
add a comment |
add a comment |
In case you need to use result
(in non-void cases) in "some generic stuff", I propose a if constexpr
based solution (so, unfortunately, not before C++17).
Not really elegant, to be honest.
First of all, detect the "true return type" of f
(given the arguments)
using TR_t = std::invoke_result_t<F, As...>;
Next a constexpr
variable to see if the returned type is void
(just to simplify a little the following code)
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
Now we define a (potentially) "fake return type": int
when the true return type is void
, the TR_t
otherwise
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
Then we define the a smart pointer to the result value as pointer to the "fake return type" (so int
in void case)
std::unique_ptr<FR_t> pResult;
Passing through a pointer, instead of a simple variable of type "fake return type", we can operate also when TR_t
isn't default constructible or not assignable (limits, pointed by Barry (thanks), of the first version of this answer).
Now, using if constexpr
, the two case to exec f
(this, IMHO, is the ugliest part because we have to write two times the same f
invocation)
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
After this, the "some generic stuff" that can use result
(in non-void cases) and also `isVoidTR).
To conclude, another if constexpr
if constexpr ( isVoidTR )
return;
else
return *pResult;
As pointed by Barry, this solution has some important downsides because (not void
cases)
- require an allocation
- require an extra copy in correspondence of the
return
- doesn't works at all if the
TR_t
(the type returned byf()
) is a reference type
Anyway, the following is a full compiling C++17 example
#include <memory>
#include <type_traits>
template <typename F, typename ... As>
auto foo (F && f, As && ... args)
// true return type
using TR_t = std::invoke_result_t<F, As...>;
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
// (possibly) fake return type
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
std::unique_ptr<FR_t> pResult;
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return *pResult;
int main ()
foo([]());
//auto a foo([]()) ; // compilation error: foo() is void
auto b foo([](auto a0, auto...) return a0; , 1, 2l, 3ll) ;
static_assert( std::is_same_v<decltype(b), int> );
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping outvoid
for a regular type, is a good one though - see my answer for a better way to implement that idea.
– Barry
May 23 at 2:45
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (foo()
never returnvoid
anymore).
– max66
May 23 at 10:26
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
|
show 16 more comments
In case you need to use result
(in non-void cases) in "some generic stuff", I propose a if constexpr
based solution (so, unfortunately, not before C++17).
Not really elegant, to be honest.
First of all, detect the "true return type" of f
(given the arguments)
using TR_t = std::invoke_result_t<F, As...>;
Next a constexpr
variable to see if the returned type is void
(just to simplify a little the following code)
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
Now we define a (potentially) "fake return type": int
when the true return type is void
, the TR_t
otherwise
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
Then we define the a smart pointer to the result value as pointer to the "fake return type" (so int
in void case)
std::unique_ptr<FR_t> pResult;
Passing through a pointer, instead of a simple variable of type "fake return type", we can operate also when TR_t
isn't default constructible or not assignable (limits, pointed by Barry (thanks), of the first version of this answer).
Now, using if constexpr
, the two case to exec f
(this, IMHO, is the ugliest part because we have to write two times the same f
invocation)
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
After this, the "some generic stuff" that can use result
(in non-void cases) and also `isVoidTR).
To conclude, another if constexpr
if constexpr ( isVoidTR )
return;
else
return *pResult;
As pointed by Barry, this solution has some important downsides because (not void
cases)
- require an allocation
- require an extra copy in correspondence of the
return
- doesn't works at all if the
TR_t
(the type returned byf()
) is a reference type
Anyway, the following is a full compiling C++17 example
#include <memory>
#include <type_traits>
template <typename F, typename ... As>
auto foo (F && f, As && ... args)
// true return type
using TR_t = std::invoke_result_t<F, As...>;
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
// (possibly) fake return type
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
std::unique_ptr<FR_t> pResult;
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return *pResult;
int main ()
foo([]());
//auto a foo([]()) ; // compilation error: foo() is void
auto b foo([](auto a0, auto...) return a0; , 1, 2l, 3ll) ;
static_assert( std::is_same_v<decltype(b), int> );
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping outvoid
for a regular type, is a good one though - see my answer for a better way to implement that idea.
– Barry
May 23 at 2:45
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (foo()
never returnvoid
anymore).
– max66
May 23 at 10:26
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
|
show 16 more comments
In case you need to use result
(in non-void cases) in "some generic stuff", I propose a if constexpr
based solution (so, unfortunately, not before C++17).
Not really elegant, to be honest.
First of all, detect the "true return type" of f
(given the arguments)
using TR_t = std::invoke_result_t<F, As...>;
Next a constexpr
variable to see if the returned type is void
(just to simplify a little the following code)
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
Now we define a (potentially) "fake return type": int
when the true return type is void
, the TR_t
otherwise
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
Then we define the a smart pointer to the result value as pointer to the "fake return type" (so int
in void case)
std::unique_ptr<FR_t> pResult;
Passing through a pointer, instead of a simple variable of type "fake return type", we can operate also when TR_t
isn't default constructible or not assignable (limits, pointed by Barry (thanks), of the first version of this answer).
Now, using if constexpr
, the two case to exec f
(this, IMHO, is the ugliest part because we have to write two times the same f
invocation)
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
After this, the "some generic stuff" that can use result
(in non-void cases) and also `isVoidTR).
To conclude, another if constexpr
if constexpr ( isVoidTR )
return;
else
return *pResult;
As pointed by Barry, this solution has some important downsides because (not void
cases)
- require an allocation
- require an extra copy in correspondence of the
return
- doesn't works at all if the
TR_t
(the type returned byf()
) is a reference type
Anyway, the following is a full compiling C++17 example
#include <memory>
#include <type_traits>
template <typename F, typename ... As>
auto foo (F && f, As && ... args)
// true return type
using TR_t = std::invoke_result_t<F, As...>;
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
// (possibly) fake return type
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
std::unique_ptr<FR_t> pResult;
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return *pResult;
int main ()
foo([]());
//auto a foo([]()) ; // compilation error: foo() is void
auto b foo([](auto a0, auto...) return a0; , 1, 2l, 3ll) ;
static_assert( std::is_same_v<decltype(b), int> );
In case you need to use result
(in non-void cases) in "some generic stuff", I propose a if constexpr
based solution (so, unfortunately, not before C++17).
Not really elegant, to be honest.
First of all, detect the "true return type" of f
(given the arguments)
using TR_t = std::invoke_result_t<F, As...>;
Next a constexpr
variable to see if the returned type is void
(just to simplify a little the following code)
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
Now we define a (potentially) "fake return type": int
when the true return type is void
, the TR_t
otherwise
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
Then we define the a smart pointer to the result value as pointer to the "fake return type" (so int
in void case)
std::unique_ptr<FR_t> pResult;
Passing through a pointer, instead of a simple variable of type "fake return type", we can operate also when TR_t
isn't default constructible or not assignable (limits, pointed by Barry (thanks), of the first version of this answer).
Now, using if constexpr
, the two case to exec f
(this, IMHO, is the ugliest part because we have to write two times the same f
invocation)
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
After this, the "some generic stuff" that can use result
(in non-void cases) and also `isVoidTR).
To conclude, another if constexpr
if constexpr ( isVoidTR )
return;
else
return *pResult;
As pointed by Barry, this solution has some important downsides because (not void
cases)
- require an allocation
- require an extra copy in correspondence of the
return
- doesn't works at all if the
TR_t
(the type returned byf()
) is a reference type
Anyway, the following is a full compiling C++17 example
#include <memory>
#include <type_traits>
template <typename F, typename ... As>
auto foo (F && f, As && ... args)
// true return type
using TR_t = std::invoke_result_t<F, As...>;
constexpr bool isVoidTR std::is_same_v<TR_t, void> ;
// (possibly) fake return type
using FR_t = std::conditional_t<isVoidTR, int, TR_t>;
std::unique_ptr<FR_t> pResult;
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
pResult.reset(new TR_tstd::forward<F>(f)(std::forward<As>(args)...));
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return *pResult;
int main ()
foo([]());
//auto a foo([]()) ; // compilation error: foo() is void
auto b foo([](auto a0, auto...) return a0; , 1, 2l, 3ll) ;
static_assert( std::is_same_v<decltype(b), int> );
edited May 23 at 13:14
answered May 22 at 19:16
max66max66
41.9k74777
41.9k74777
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping outvoid
for a regular type, is a good one though - see my answer for a better way to implement that idea.
– Barry
May 23 at 2:45
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (foo()
never returnvoid
anymore).
– max66
May 23 at 10:26
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
|
show 16 more comments
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping outvoid
for a regular type, is a good one though - see my answer for a better way to implement that idea.
– Barry
May 23 at 2:45
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (foo()
never returnvoid
anymore).
– max66
May 23 at 10:26
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping out
void
for a regular type, is a good one though - see my answer for a better way to implement that idea.– Barry
May 23 at 2:45
This solution imposes default construction and assignment as requirements (in addition to lots of boilerplate). The underlying idea, swapping out
void
for a regular type, is a good one though - see my answer for a better way to implement that idea.– Barry
May 23 at 2:45
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (
foo()
never return void
anymore).– max66
May 23 at 10:26
@Barry - Yes, this solution require a lots of boilerplate but permit to use the computed value in the "generic stuff"; my other solution is trivially simple but the result is unusable; taking this in account, I think the boilerplate is justifiable. You're right about default contruction and assignment; i was thinking of add a caveat in the answer but there is a simple way (a little boilerplate more) to avoid this problem; answer modified. Anyway, I find your solution interesting but really different because change the signature of the function (
foo()
never return void
anymore).– max66
May 23 at 10:26
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
... and now it requires an extra copy and a full allocation!
– Barry
May 23 at 12:08
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
@Barry - yes, but avoid the problem of the default construction and assignment; the extra copy is about a pointer.
– max66
May 23 at 12:12
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
The extra copy is not a pointer, it's the full object. This inhibits RVO.
– Barry
May 23 at 12:16
|
show 16 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%2f56256640%2ffunction-argument-returning-void-or-non-void-type%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
4
offtopic: this
std::forward
is misused. It should be used only if argument can be moved to next function/template. It doesn't break anything here it is just boilerplate.– Marek R
May 22 at 12:31
After your edit: How can this possibly work with
void
return types? Either you assign the return value toresult
or you leave it.auto return = some_void_func()
doesn't make any sense.– andreee
May 22 at 12:31
4
@MarekR what if function object with rvalue only overload of
operator()
is passed? Will it work withoutforward
?– bartop
May 22 at 12:35
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, so
std::forward
can have sense here.– Marek R
May 22 at 12:44
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
May 22 at 13:19