Like Tree2Likes

How to make a function similar to std::thread's constructor ?

This is a discussion on How to make a function similar to std::thread's constructor ? within the C++ Programming forums, part of the General Programming Boards category; I have a class, called Program, and I want a member function which can take any type of function object ...

  1. #1
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498

    How to make a function similar to std::thread's constructor ?

    I have a class, called Program, and I want a member function which can take any type of function object (along with arguments as the other arguments), which will be executed after some object specific initialization, and perhaps return the value it was suppossed to.
    I can't figure out what to do about the arguments.
    Code:
    template <typename Function,typename ReturnType>
    ReturnType Program::useFor(Function foo)
    {
        glUseProgram(this->handle);
        ReturnType value = foo();
        glUseProgram(0);
        return value;
    }
    I know that it is possible without imposing restrictions on the argument (like it must take a std::tuple).
    The design of std::thread's constructor is exactly what I'm aiming for.
    Code:
    auto func = [](int x){std::cout<<x;};
    std::thread myThread(func,5);
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,214
    O_o

    The constructor for the version of `std::thread' with C++11 features uses variable argument templates where the code is essentially `function(args)'.

    The constructor for the version of `std::thread' without C++11 features must be emulated with a fixed upper count of option parameters where the code is as manually verbose as you should expect.

    That said, please don't do this. Duplication of fiddly mechanisms for the sake of convenience when that convenience is already self-contained is terribly flawed design.

    Code:
    useFor(bind([](int x){std::cout<<x;}, 5));
    That code isn't sufficiently more difficult to use and use well than a version directly duplicating part of that functionality, and that code uses a standard (C++) feature which breeds compatibility from familiarity.

    Soma

  3. #3
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    Quote Originally Posted by phantomotap View Post
    Code:
    useFor(bind([](int x){std::cout<<x;}, 5));
    That code isn't sufficiently more difficult to use and use well than a version directly duplicating part of that functionality, and that code uses a standard (C++) feature which breeds compatibility from familiarity.
    Great! That should work.

    Though, this ( std::bind - cppreference.com ) says that it is implemented with variable argument templates itself.
    So, why shouldn't I do that myself ?
    (I've no intention to make the code C++03 compatible, so I could use variable argument templates without second thoughts, right ?)
    Last edited by manasij7479; 06-17-2012 at 05:50 AM.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  4. #4
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,214
    Though, this ( std::bind - cppreference.com ) says that it is implemented with variable argument templates itself.
    So, why shouldn't I do that myself ?
    The `std::bind' function and related facilities are implemented with C++11 features where the compiler also supports those features.

    What you seem to be missing is that those facilities have a heavily compatible (as written in the standard) and widely available library implementation which can make your code using those facilities instead more robust by virtue.

    So, why shouldn't I do that myself ?
    Duplicating any functionality for fun or education is a solid win.

    Duplicating convenience functionality that is already available in a self-contained package is really bad design. There is no reason for you to recreate it when the facilities that you might carefully use to offer similar functionality is widely available and well understood.

    I've no intention to make the code C++03 compatible, so I could use variable argument templates without second thoughts, right ?
    O_o

    You are a programmer. You shouldn't do anything without second, third, and fourth thoughts.

    As I've repeatedly said on this forum and elsewhere, we don't know what the eventual landscape will be despite what the standard currently says about C++11 features. It will be at least a couple more years before a standard for using the standard reveals itself through experimenting how compiler vendors have decided to support those features.

    For now, using C++11 features doesn't allow you to boil your code down to a widely available standard as implemented. Despite what some people think, that is a huge issue for everybody writing code using C++11 features. You can't yet start using a feature in such a way as to be reliably portable for the future.

    Nothing of that should suggest to you that I think that none should be using C++11 features. You absolutely can use C++11 features. You just need to be aware of what it may cost you in maintaining code that uses those features when the landscape changes.

    If you start using variable argument templates in your code now you will find that some compilers don't support them. You'll also discover that some compilers have a hard time expanding them in certain contexts making them less useful than they might be. You'll also find that some vendors have no intention to support variable argument templates because the vendor considers them "not worth the effort". You'll also find, in time, that what the standard says about them is only a baseline where it is actually the real would that will determine their potential.

    So again, feel free to use variable argument templates and implement this directly. Just make sure that you are aware that:

    Duplicating convenience functionality that is already available in a self-contained package is bad design.
    You will not necessarily get to use your code on any other compiler just because it has "C++11 support".
    Doing this without C++11 features is ugly and verbose.
    The standard has already done a large part of this for you because compatible features exist without C++11 support (`std::bind' for some compilers and `boost::bind' and still others).

    Soma
    manasij7479 likes this.

  5. #5
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    Okay, so I'll use std::bind and can essentially forget about arguments.
    I hope there aren't similar issues about the return value.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,214
    [Edit]Well, for some reason, my edits aren't sticking again![/Edit]

    [Edit]
    I hope there aren't similar issues about the return value.
    Okay, let's try an edit!

    Anyway, you can query types associated with what is returned by `std::bind' for the return type. If you do it with traits (`return_type<TemplateParameterType>::type' or similar') you should not have any problems in that direction.
    [/Edit]

    For the sake of completeness, you can do what I do for new code and optionally provide extra features through "CPP" and let the client shoot themselves when compiler support is not as close to the standard as it might be.

    I still would not provide this overload directly because it is still bad design to duplicate it when a self-contained option is so readily available, but if you must provide it at least provide it as an extension in such contexts.

    Note: Please don't even bother trying to poll the value of `__cplusplus' or other macros. Vendors are notoriously "I don't care." for those things.

    Code:
    #if !defined(BOOST_NO_VARIADIC_TEMPLATES)
    // You can add an overload for the functionality you desire here.
    #endif //-! (BOOST_NO_VARIADIC_TEMPLATES)
    Soma
    Last edited by phantomotap; 06-17-2012 at 06:29 AM.

  7. #7
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    Quote Originally Posted by phantomotap View Post
    Anyway, you can query types associated with what is returned by `std::bind' for the return type. If you do it with traits (`return_type<TemplateParameterType>::type' or similar') you should not have any problems in that direction.
    Can you clarify that ?
    I tried a lot of things, but can't get the syntax right.
    (And "can't deduce the ReturnType" the rest of the times. )

    [Edit: I got rid of the return type and put void as a temporary measure, but it'd be nice to have a generic solution for *all* functions ]
    Last edited by manasij7479; 06-17-2012 at 08:07 AM.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,214
    O_o

    Perhaps you should spend more time getting to know the features you are trying to use and emulate?

    Code:
    template
    <
        typename FFunction
    >
    typename result_of<FFunction()>::type Test
    (
        FFunction fFunction
    );
    
    template
    <
        typename FFunction
      , typename FParameter
    >
    typename result_of<FFunction(FParameter)>::type Test
    (
        FFunction fFunction
      , FParameter fParameter
    );
    That should give you the idea of how to go about where `std::result_of' is available. (Which, like `std::bind', a part of `std::result_of' can be emulated without C++11 features.)

    Soma
    manasij7479 likes this.

  9. #9
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    Quote Originally Posted by phantomotap View Post
    O_o

    Perhaps you should spend more time getting to know the features you are trying to use and emulate?
    I would, only if I knew which features I'm going to use and emulate!
    ---
    Thanks a lot, this works:
    Code:
            template <typename Function>
            typename std::result_of<Function()>::type useFor(Function foo)
            {
                glUseProgram(this->handle);
                typename std::result_of<Function()>::type result = foo();
                glUseProgram(0);
                return result;
            }
    Any more mishaps ?
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  10. #10
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    Quote Originally Posted by manasij7479 View Post
    I would, only if I knew which features I'm going to use and emulate!
    ---
    Thanks a lot, this works:
    Code:
            template <typename Function>
            typename std::result_of<Function()>::type useFor(Function foo)
            {
                glUseProgram(this->handle);
                typename std::result_of<Function()>::type result = foo();
                glUseProgram(0);
                return result;
            }
    Any more mishaps ?
    Actually, it worked, because I did not rebuild.
    So, it fails when the return type is actually void.
    Edit 1: So, I've to try specializing the void case.
    Edit 2: That results in an ambigous overload.
    Edit 3: This doesn't work too. ( Compiler complains about the else case, that foo() should not return anything, even when it is unreachable for the void case )
    Code:
            template <typename Function>
            typename std::result_of<Function()>::type useFor(Function foo)
            {
                glUseProgram(this->handle);
                if( std::is_void<typename std::result_of<Function()>::type>::value )
                {
                    foo();
                    glUseProgram(0);
                    return;
                }
                else 
                {
                    typename std::result_of<Function()>::type result =foo();
                    glUseProgram(0);
                    return result;
                    
                }
            }
    Edit: I think std::conditional and/or std::enable_if can help, but can't figure out how to use them here.

    (I could have two different functions for void and non void, but that is a kludgy solution ! )
    Last edited by manasij7479; 06-17-2012 at 11:01 AM.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  11. #11
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,214
    I could have two different functions for void and non void, but that is a kludgy solution !
    O_o

    You have created this situation for yourself That said, it is not really a kludgy solution because `void' variables isn't a thing; only semantically and syntactically valid statements must be used in the code writing the function you want to write which means two different bits of code at some level because you have two entirely different things you want to do.

    A conforming compiler will allow you to "return" a "void statement" with a "return expression". You aren't doing that because you are declaring a variable with assignment and trying to return that which is a named "void variable" (which isn't even a thing).

    The problem is the way compilers examine code. You can't circumvent that checking with `std::conditional' or `std::enable_if'. Those are simply tools that can be used to build what you want without simply duplicating the entire function.

    You can explore this code for three different options.

    The first is used everywhere within the C++ standard library. (It isn't specified in the standard. It is a thing that is required by implementations of the standard library.)

    The second is used by a lot of "meta-programmers" because it isolates the mechanism behind the scenes where it can be used directly instead of partially duplicating the mechanics of the function as with the first method.

    The third is used by a crazy huge number of "WIN32API" programmers where arbitrary cleanup must be done regardless of how control leaves a function which isn't a bad choice in the case where the lamda throws an exception.

    Soma

    Code:
    #include <functional>
    #include <iostream>
    
    using namespace std;
    
    /*****************************************************************/
    /*****************************************************************/
    /*****************************************************************/
    
    struct MTrue{};
    struct MFalse{};
    
    template
    <
        typename FFunction
    >
    struct MIsVoid
    {
        typedef MFalse UResult;
    };
    
    template <> struct MIsVoid<void>
    {
        typedef MTrue UResult;
    };
    
    /*****************************************************************/
    /*****************************************************************/
    /*****************************************************************/
    
    template
    <
        typename FFunction
      , typename FResult
    >
    void TestImplementation
    (
        FFunction fFunction
      , MTrue
    )
    {
        // prefix
        fFunction(); // This SHOULD be bound so to prevent duplication of forwarding.
        // postfix
    }
    
    template
    <
        typename FFunction
      , typename FResult
    >
    FResult TestImplementation
    (
        FFunction fFunction
      , MFalse
    )
    {
        // prefix
        FResult sResult(fFunction()); // This SHOULD be bound so to prevent duplication of forwarding.
        // postfix
        return(sResult);
    }
    
    template
    <
        typename FFunction
    >
    typename result_of<FFunction()>::type TestInterface
    (
        FFunction fFunction
    )
    {
        typedef typename result_of<FFunction()>::type result_type;
        typedef typename MIsVoid<typename result_of<FFunction()>::type>::UResult IsVoid;
        // You SHOULD bind all parameters for the sake of portability.
        // You MUST do this in one line for the sake of portability.
        return(TestImplementation<FFunction, result_type>(fFunction, (IsVoid())));
    }
    
    /*****************************************************************/
    /*****************************************************************/
    /*****************************************************************/
    
    struct MVoid{};
    
    template
    <
        typename FFunction
      , typename FResult
    >
    void ConditionallyAssign
    (
        FResult & fResult
      , FFunction fFunction
      , MTrue
    )
    {
        fFunction(); // This SHOULD be bound so to prevent duplication of forwarding.
    }
    
    template
    <
        typename FFunction
      , typename FResult
    >
    void ConditionallyAssign
    (
        FResult & fResult
      , FFunction fFunction
      , MFalse
    )
    {
        fResult = fFunction(); // This SHOULD be bound so to prevent duplication of forwarding.
    }
    
    template
    <
        typename FFunction
    >
    typename conditional<is_void<typename result_of<FFunction()>::type>::value, MVoid, typename result_of<FFunction()>::type>::type TestWrapped
    (
        FFunction fFunction
    )
    {
        typedef typename result_of<FFunction()>::type result_type;
        typedef typename MIsVoid<typename result_of<FFunction()>::type>::UResult IsVoid;
        typename conditional<is_void<typename result_of<FFunction()>::type>::value, MVoid, typename result_of<FFunction()>::type>::type result;
        // prefix
        // You SHOULD bind all parameters for the sake of portability.
        ConditionallyAssign(result, fFunction, (IsVoid()));
        // postfix
        return(result);
    }
    
    /*****************************************************************/
    /*****************************************************************/
    /*****************************************************************/
    
    template
    <
        typename FCleanup
    >
    struct CleanupAtExit
    {
        CleanupAtExit
        (
            FCleanup fCleanup
        ):
            mCleanup(fCleanup)
        {
        }
        ~CleanupAtExit()
        {
            mCleanup();
        }
        FCleanup mCleanup;
    };
    
    template
    <
        typename FCleanup
    >
    CleanupAtExit<FCleanup> CraftCleanup
    (
        FCleanup fCleanup
    )
    {
        return(fCleanup);
    }
    
    template
    <
        typename FFunction
    >
    typename result_of<FFunction()>::type TestCleanup
    (
        FFunction fFunction
    )
    {
        typedef typename result_of<FFunction()>::type result_type;
        typedef typename MIsVoid<typename result_of<FFunction()>::type>::UResult IsVoid;
        //prefix
        auto sCleaner(/*postfix*/CraftCleanup([](){})/*postfix*/);
        // You SHOULD bind all parameters for the sake of portability.
        // You MUST do this in one line for the sake of portability.
        return(TestImplementation<FFunction, result_type>(fFunction, (IsVoid())));
    }
    
    /*****************************************************************/
    /*****************************************************************/
    /*****************************************************************/
    
    void Tester1()
    {
        cout << 1 << '\n';
    }
    
    int Tester2()
    {
        cout << 2 << '\n';
        return(2);
    }
    
    int main()
    {
        auto sFunction1 = [](){cout << 1 << '\n';};
        auto sFunction2 = [](){cout << 2 << '\n'; return(2);};
        TestInterface(sFunction1);
        cout << '\n';
        TestInterface(Tester1);
        cout << '\n';
        TestInterface(sFunction2);
        cout << '\n';
        TestInterface(Tester2);
        cout << '\n';
        TestWrapped(sFunction1);
        cout << '\n';
        TestWrapped(Tester1);
        cout << '\n';
        TestWrapped(sFunction2);
        cout << '\n';
        TestWrapped(Tester2);
        cout << '\n';
        TestCleanup(sFunction1);
        cout << '\n';
        TestCleanup(Tester1);
        cout << '\n';
        TestCleanup(sFunction2);
        cout << '\n';
        TestCleanup(Tester2);
        cout << '\n';
        return(0);
    }

  12. #12
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    That's a bit overwhelming, and makes me feel like I just started learning C++ yesterday !

    Let me examine that for a few days and get confident with the related concepts.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  13. #13
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,214
    That's a bit overwhelming, and makes me feel like I just started learning C++ yesterday!
    ^_^

    I did give you fair warning.

    *shrug*

    Using generic facilities is often very easy.

    Creating a generic facility is often very hard.

    By the by, this exact situation (executing prefix and postfix code around an invocation) is so common that it has a name, widely available tools, and established naming themes so you may as well get used to dealing with the problems that show up when writing generic facilities if you plan on developing more in the future.

    Soma

    Simple threaded summation, review please.

  14. #14
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    I 'think' I now understand how those work (Some questions to follow, in future) and have chosen an approach similar to the last option..
    (A little less generic, for now; which will change if I use the same code in multiple classes. )
    Code:
            template <typename Function>
            typename  std::result_of<Function()>::type
            useFor(Function foo)
            {
                Use use(handle);
                return foo();
            }
    Where Use (CleanupAtExit sounds a little fluffy !) is a private subclass of the main class, that executes the prefix anad postfix in the constructor and destructor respectively, thus simplifying the logic.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



  15. #15
    Registered User manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    Kolkata@India
    Posts
    2,498
    Quote Originally Posted by phantomotap View Post
    A conforming compiler will allow you to "return" a "void statement" with a "return expression". You aren't doing that because you are declaring a variable with assignment and trying to return that which is a named "void variable" (which isn't even a thing).
    It should be made valid, I think.
    I can can argue that the nullptr doesn't semantically mean anything (Points to nothing and Contains nothing, I see an analogy). But it still exists.
    Though, a void value (like None in python), would be much less useful in a strongly typed language, it could have uses.. say.. determining, if something is acessible, or if it has been 'moved' .
    Last edited by manasij7479; 06-19-2012 at 12:45 PM.
    Manasij Mukherjee | gcc-4.8.2 @Arch Linux
    Slow and Steady wins the race... if and only if :
    1.None of the other participants are fast and steady.
    2.The fast and unsteady suddenly falls asleep while running !



Page 1 of 2 12 LastLast
Popular pages Recent additions subscribe to a feed

Similar Threads

  1. writing a read_line function similar to (scanf or gets)
    By Nyah Check in forum C Programming
    Replies: 9
    Last Post: 01-05-2012, 12:18 PM
  2. function similar to gotoxy()
    By juice in forum C Programming
    Replies: 4
    Last Post: 12-25-2011, 12:42 PM
  3. function similar to execvp()
    By Elkvis in forum Linux Programming
    Replies: 10
    Last Post: 09-22-2011, 11:25 AM
  4. Similar thread: More token counting
    By Imanuel in forum C Programming
    Replies: 3
    Last Post: 07-21-2010, 04:54 AM
  5. function similar to strpos() ?
    By willc0de4food in forum C Programming
    Replies: 18
    Last Post: 10-08-2005, 04:13 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21