Function argument returning void or non-void typeWhat 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 stringC++: return the return value of a void function from a void functionC++ template function that takes a function type with specific return typeVariadic function wrapper for any return type
How does the Earth's center produce heat?
I know that there is a preselected candidate for a position to be filled at my department. What should I do?
When playing Edgar Markov, what is the definition of a "Vampire spell"?
How to patch glass cuts in a bicycle tire?
How to let other coworkers know that I don't share my coworker's political views?
Must a warlock replace spells with new spells of exactly their Pact Magic spell slot level?
How to make the Bass in SATB move more smoothly?
Can we assume that a hash function with high collision resistance also means highly uniform distribution?
Is there a single word meaning "the thing that attracts me"?
Why isn't 'chemically-strengthened glass' made with potassium carbonate to begin with?
Beginner looking to learn/master musical theory and instrumental ability. Where should I begin?
Natural Armour and Weapons
Do photons bend spacetime or not?
Which European Languages are not Indo-European?
Writing style before Elements of Style
Why was this character made Grand Maester?
How was Daenerys able to legitimise this character?
What would prevent living skin from being a good conductor for magic?
Gravitational Force Between Numbers
Dad jokes are fun
“For nothing” = “pour rien”?
Grade-school elementary algebra presented in an abstract-algebra style?
Is there a simple example that empirical evidence is misleading?
Drums and punctuation
Function argument returning void or non-void type
What 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 stringC++: return the return value of a void function from a void functionC++ template function that takes a function type with specific return typeVariadic 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 middle of writing some generic code for future library. I came across following problem inside template function. Imagine following code:
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 function that returns void like that
foo([]());
Now of course I could use some std::enable_if magic to check return type and make specialization for function returning void look 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 function. Can this be done easily in generic way for both void-returning and non-void-returning functions in 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 middle of writing some generic code for future library. I came across following problem inside template function. Imagine following code:
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 function that returns void like that
foo([]());
Now of course I could use some std::enable_if magic to check return type and make specialization for function returning void look 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 function. Can this be done easily in generic way for both void-returning and non-void-returning functions in 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
1
offtopic: thisstd::forwardis 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
7 hours ago
After your edit: How can this possibly work withvoidreturn types? Either you assign the return value toresultor you leave it.auto return = some_void_func()doesn't make any sense.
– andreee
7 hours ago
3
@MarekR what if function object with rvalue only overload ofoperator()is passed? Will it work withoutforward?
– bartop
7 hours ago
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, sostd::forwardcan have sense here.
– Marek R
7 hours ago
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
7 hours ago
add a comment |
I am in middle of writing some generic code for future library. I came across following problem inside template function. Imagine following code:
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 function that returns void like that
foo([]());
Now of course I could use some std::enable_if magic to check return type and make specialization for function returning void look 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 function. Can this be done easily in generic way for both void-returning and non-void-returning functions in 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 middle of writing some generic code for future library. I came across following problem inside template function. Imagine following code:
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 function that returns void like that
foo([]());
Now of course I could use some std::enable_if magic to check return type and make specialization for function returning void look 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 function. Can this be done easily in generic way for both void-returning and non-void-returning functions in 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 7 hours ago
max66
41.2k74777
41.2k74777
asked 8 hours ago
bartopbartop
3,5821133
3,5821133
1
offtopic: thisstd::forwardis 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
7 hours ago
After your edit: How can this possibly work withvoidreturn types? Either you assign the return value toresultor you leave it.auto return = some_void_func()doesn't make any sense.
– andreee
7 hours ago
3
@MarekR what if function object with rvalue only overload ofoperator()is passed? Will it work withoutforward?
– bartop
7 hours ago
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, sostd::forwardcan have sense here.
– Marek R
7 hours ago
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
7 hours ago
add a comment |
1
offtopic: thisstd::forwardis 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
7 hours ago
After your edit: How can this possibly work withvoidreturn types? Either you assign the return value toresultor you leave it.auto return = some_void_func()doesn't make any sense.
– andreee
7 hours ago
3
@MarekR what if function object with rvalue only overload ofoperator()is passed? Will it work withoutforward?
– bartop
7 hours ago
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, sostd::forwardcan have sense here.
– Marek R
7 hours ago
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
7 hours ago
1
1
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
7 hours ago
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
7 hours ago
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
7 hours ago
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
7 hours ago
3
3
@MarekR what if function object with rvalue only overload of
operator() is passed? Will it work without forward?– bartop
7 hours ago
@MarekR what if function object with rvalue only overload of
operator() is passed? Will it work without forward?– bartop
7 hours ago
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
7 hours ago
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
7 hours ago
1
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
7 hours ago
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
7 hours ago
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
7 hours ago
1
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
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 aFinallyraii code, as proposed in another answer.
– Jarod42
7 hours ago
@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
7 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
An extra copy could be avoided by havingfoo()return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
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 result value as "fake return type" (so int in void case)
FR_t result ;
Then a (void) instruction to avoid a possible "set but not used" warning
(void)result;
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
result = std::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 result;
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>;
FR_t result ; // is int in case of void
(void)result; // to avoid "set but not used" warning, in void case
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
result = std::forward<F>(f)(std::forward<As>(args)...);
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return result;
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> );
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%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
7 hours ago
1
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
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
7 hours ago
1
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
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 7 hours ago
answered 8 hours ago
max66max66
41.2k74777
41.2k74777
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
7 hours ago
1
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
add a comment |
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
7 hours ago
1
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
2
2
bonus tip: the "generic stuff" should better not throw anything
– Drax
7 hours ago
bonus tip: the "generic stuff" should better not throw anything
– Drax
7 hours ago
1
1
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
@Drax - good point; thanks; added in the answer.
– max66
7 hours ago
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 aFinallyraii code, as proposed in another answer.
– Jarod42
7 hours ago
@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
7 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
An extra copy could be avoided by havingfoo()return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
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 aFinallyraii code, as proposed in another answer.
– Jarod42
7 hours ago
@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
7 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
An extra copy could be avoided by havingfoo()return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
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 7 hours ago
Sam VarshavchikSam Varshavchik
64.6k53783
64.6k53783
"Some specialization, somewhere, is necessary." not with aFinallyraii code, as proposed in another answer.
– Jarod42
7 hours ago
@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
7 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
An extra copy could be avoided by havingfoo()return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
add a comment |
"Some specialization, somewhere, is necessary." not with aFinallyraii code, as proposed in another answer.
– Jarod42
7 hours ago
@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
7 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
An extra copy could be avoided by havingfoo()return the helper object itself. Which will necessitate changing the callers.
– Sam Varshavchik
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
"Some specialization, somewhere, is necessary." not with a
Finally raii code, as proposed in another answer.– Jarod42
7 hours ago
"Some specialization, somewhere, is necessary." not with a
Finally raii code, as proposed in another answer.– Jarod42
7 hours ago
@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
7 hours ago
@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
7 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
This incurs an unconditional extra copy.
– Barry
6 hours ago
An extra copy could be avoided by having
foo() return the helper object itself. Which will necessitate changing the callers.– Sam Varshavchik
5 hours ago
An extra copy could be avoided by having
foo() return the helper object itself. Which will necessitate changing the callers.– Sam Varshavchik
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
@SamVarshavchik That's... not a solution.
– Barry
5 hours ago
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 6 hours ago
BarryBarry
190k21340625
190k21340625
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 result value as "fake return type" (so int in void case)
FR_t result ;
Then a (void) instruction to avoid a possible "set but not used" warning
(void)result;
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
result = std::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 result;
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>;
FR_t result ; // is int in case of void
(void)result; // to avoid "set but not used" warning, in void case
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
result = std::forward<F>(f)(std::forward<As>(args)...);
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return result;
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> );
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 result value as "fake return type" (so int in void case)
FR_t result ;
Then a (void) instruction to avoid a possible "set but not used" warning
(void)result;
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
result = std::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 result;
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>;
FR_t result ; // is int in case of void
(void)result; // to avoid "set but not used" warning, in void case
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
result = std::forward<F>(f)(std::forward<As>(args)...);
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return result;
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> );
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 result value as "fake return type" (so int in void case)
FR_t result ;
Then a (void) instruction to avoid a possible "set but not used" warning
(void)result;
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
result = std::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 result;
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>;
FR_t result ; // is int in case of void
(void)result; // to avoid "set but not used" warning, in void case
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
result = std::forward<F>(f)(std::forward<As>(args)...);
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return result;
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 result value as "fake return type" (so int in void case)
FR_t result ;
Then a (void) instruction to avoid a possible "set but not used" warning
(void)result;
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
result = std::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 result;
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>;
FR_t result ; // is int in case of void
(void)result; // to avoid "set but not used" warning, in void case
if constexpr ( isVoidTR )
std::forward<F>(f)(std::forward<As>(args)...);
else
result = std::forward<F>(f)(std::forward<As>(args)...);
// some generic stuff (potentially depending from result,
// in non-void cases)
if constexpr ( isVoidTR )
return;
else
return result;
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> );
answered 1 hour ago
max66max66
41.2k74777
41.2k74777
add a comment |
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%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
1
offtopic: this
std::forwardis 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
7 hours ago
After your edit: How can this possibly work with
voidreturn types? Either you assign the return value toresultor you leave it.auto return = some_void_func()doesn't make any sense.– andreee
7 hours ago
3
@MarekR what if function object with rvalue only overload of
operator()is passed? Will it work withoutforward?– bartop
7 hours ago
OK fair point, it is possible that someone can define such operator. This is quite unusual, but possible, so
std::forwardcan have sense here.– Marek R
7 hours ago
1
related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html
– geza
7 hours ago