How to guarantee order of argument evaluation when calling a function object?

前端 未结 4 1736
深忆病人
深忆病人 2020-11-27 16:41

The answers to the question on how to avoid undefined execution order for the constructors when using std::make_tuple led to a discussion during which I learned that the ord

4条回答
  •  遥遥无期
    2020-11-27 17:34

    First, I think if the order does matter it's probably best to explicitly construct those elements prior to the call, then pass them in. Much easier to read, but far less fun!

    This is just expanding on Kerrek's answer:

    #include 
    
    namespace detail
    {
        // the ultimate end result of the call;
        // replaceable with std::result_of? I do not know.
        template 
        static auto ordered_call_result(F&& f, Args&&... args)
            -> decltype(std::forward(f)
                        (std::forward(args)...)); // not defined
    
        template 
        class ordered_call_helper
        {
        public:
            template 
            ordered_call_helper(F&& f, Args&&... args) :
            mResult(std::forward(f)(std::forward(args)...))
            {}
    
            operator R()
            {
                return std::move(mResult);
            }
    
        private:
            R mResult;
        };
    
        template <>
        class ordered_call_helper
        {
        public:
            template 
            ordered_call_helper(F&& f, Args&&... args)
            {
                std::forward(f)(std::forward(args)...);
            }
        };
    
        // perform the call then coax out the result member via static_cast,
        // which also works nicely when the result type is void (doing nothing)
        #define ORDERED_CALL_DETAIL(r, f, ...) \
                static_cast(detail::ordered_call_helper{f, __VA_ARGS__})
    };
    
    // small level of indirection because we specify the result type twice
    #define ORDERED_CALL(f, ...) \
            ORDERED_CALL_DETAIL(decltype(detail::ordered_call_result(f, __VA_ARGS__)), \
                                f, __VA_ARGS__)
    

    And an example:

    #include 
    
    int add(int x, int y, int z)
    {
        return x + y + z;
    }
    
    void print(int x, int y, int z)
    {
        std::cout << "x: " << x << " y: " << y << " z: " << z << std::endl;
    }
    
    int get_x() { std::cout << "[x]"; return 11; }
    int get_y() { std::cout << "[y]"; return 16; }
    int get_z() { std::cout << "[z]"; return 12; }
    
    int main()
    {
        print(get_x(), get_y(), get_z());
        std::cout << "sum: " << add(get_x(), get_y(), get_z()) << std::endl;
    
        std::cout << std::endl;   
    
        ORDERED_CALL(print, get_x(), get_y(), get_z());
        std::cout << "sum: " << ORDERED_CALL(add, get_x(), get_y(), get_z()) << std::endl;
    
        std::cout << std::endl;
    
        int verify[] = { get_x(), get_y(), get_z() };
    }
    

    That last line is there to verify brace initializers do in fact have effect, normally.

    Unfortunately as has been discovered from other answers/comments, GCC does not get it right so I cannot test my answer. Additionally, MSVC Nov2012CTP also does not get it right (and has a nasty bug that chokes on the ordered_call_result†). If someone wants to test this with clang, that would be swell.

    †For this particular example, the trailing return type can be decltype(f(0, 0, 0)) instead.

提交回复
热议问题