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;








18















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*/);










share|improve this question



















  • 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 to result 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 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






  • 1





    related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html

    – geza
    May 22 at 13:19

















18















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*/);










share|improve this question



















  • 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 to result 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 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






  • 1





    related proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0146r1.html

    – geza
    May 22 at 13:19













18












18








18








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*/);










share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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: 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






  • 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











  • 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












  • 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 to result 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 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






  • 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












4 Answers
4






active

oldest

votes


















14














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.






share|improve this answer




















  • 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


















9














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;






share|improve this answer























  • "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












  • 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











  • @SamVarshavchik That's... not a solution.

    – Barry
    May 22 at 14:37


















5














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.






share|improve this answer






























    -1














    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 by f()) 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> );







    share|improve this answer

























    • 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











    • ... 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











    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
    );



    );













    draft saved

    draft discarded


















    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









    14














    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.






    share|improve this answer




















    • 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















    14














    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.






    share|improve this answer




















    • 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













    14












    14








    14







    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.






    share|improve this answer















    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.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    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












    • 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













    9














    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;






    share|improve this answer























    • "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












    • 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











    • @SamVarshavchik That's... not a solution.

      – Barry
      May 22 at 14:37















    9














    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;






    share|improve this answer























    • "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












    • 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











    • @SamVarshavchik That's... not a solution.

      – Barry
      May 22 at 14:37













    9












    9








    9







    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;






    share|improve this answer













    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;







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered May 22 at 12:36









    Sam VarshavchikSam Varshavchik

    64.7k53783




    64.7k53783












    • "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












    • 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











    • @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











    • @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











    • 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
















    "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











    5














    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.






    share|improve this answer



























      5














      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.






      share|improve this answer

























        5












        5








        5







        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.






        share|improve this answer













        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.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered May 22 at 14:12









        BarryBarry

        192k21350635




        192k21350635





















            -1














            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 by f()) 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> );







            share|improve this answer

























            • 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











            • ... 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















            -1














            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 by f()) 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> );







            share|improve this answer

























            • 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











            • ... 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













            -1












            -1








            -1







            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 by f()) 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> );







            share|improve this answer















            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 by f()) 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> );








            share|improve this answer














            share|improve this answer



            share|improve this answer








            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 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











            • ... 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












            • @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












            • @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

















            draft saved

            draft discarded
















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            Wikipedia:Vital articles Мазмуну Biography - Өмүр баян Philosophy and psychology - Философия жана психология Religion - Дин Social sciences - Коомдук илимдер Language and literature - Тил жана адабият Science - Илим Technology - Технология Arts and recreation - Искусство жана эс алуу History and geography - Тарых жана география Навигация менюсу

            Bruxelas-Capital Índice Historia | Composición | Situación lingüística | Clima | Cidades irmandadas | Notas | Véxase tamén | Menú de navegacióneO uso das linguas en Bruxelas e a situación do neerlandés"Rexión de Bruxelas Capital"o orixinalSitio da rexiónPáxina de Bruselas no sitio da Oficina de Promoción Turística de Valonia e BruxelasMapa Interactivo da Rexión de Bruxelas-CapitaleeWorldCat332144929079854441105155190212ID28008674080552-90000 0001 0666 3698n94104302ID540940339365017018237

            What should I write in an apology letter, since I have decided not to join a company after accepting an offer letterShould I keep looking after accepting a job offer?What should I do when I've been verbally told I would get an offer letter, but still haven't gotten one after 4 weeks?Do I accept an offer from a company that I am not likely to join?New job hasn't confirmed starting date and I want to give current employer as much notice as possibleHow should I address my manager in my resignation letter?HR delayed background verification, now jobless as resignedNo email communication after accepting a formal written offer. How should I phrase the call?What should I do if after receiving a verbal offer letter I am informed that my written job offer is put on hold due to some internal issues?Should I inform the current employer that I am about to resign within 1-2 weeks since I have signed the offer letter and waiting for visa?What company will do, if I send their offer letter to another company