19

关于在使用 std::make_tuple 时如何避免构造函数的未定义执行顺序的问题的答案引发了一次讨论,在该讨论中我了解到可以保证构造函数的参数评估顺序:Using abraced-init-list the order保证从左到右:

T{ a, b, c }

表达式abc以给定的顺序计算。情况就是这样,即使该类型T只定义了一个普通的构造函数。

显然,并非所有被调用的东西都是构造函数,有时在调用函数时保证评估顺序会很好,但是没有像大括号参数列表这样的东西来调用具有定义的参数评估顺序的函数。问题变成了:对构造函数的保证是否可以用来构建一个函数调用工具(“ function_apply()”),并为参数的评估提供排序保证?要求调用函数对象是可以接受的。

4

4 回答 4

15

像这样一个愚蠢的包装类呢:

struct OrderedCall
{
    template <typename F, typename ...Args>
    OrderedCall(F && f, Args &&... args)
    {
        std::forward<F>(f)(std::forward<Args>(args)...);
    }
};

用法:

void foo(int, char, bool);

OrderedCall{foo, 5, 'x', false};

如果你想要一个返回值,你可以通过引用传递它(你需要一些特征来提取返回类型),或者将它存储在对象中,以获得如下接口:

auto x = OrderedCall{foo, 5, 'x', false}.get_result();
于 2012-12-27T17:25:16.087 回答
5

我提出的解决方案是std::tuple<...>将参数放在一起,而不是使用该对象的元素调用函数对象。好处是可以推导出返回类型。实际的具体逻辑是这样的:

template <typename F, typename T, int... I>
auto function_apply(F&& f, T&& t, indices<I...> const*)
    -> decltype(f(std::get<I>(t)...)) {
    f(std::get<I>(t)...);
}

template <typename F, typename T>
auto function_apply(F&& f, T&& t)
    -> decltype(function_apply(std::forward<F>(f), std::forward<T>(t),
                               make_indices<T>())) {
    function_apply(std::forward<F>(f), std::forward<T>(t),
                   make_indices<T>());
}

...使用如下表达式调用:

void f(int i, double d, bool b) {
    std::cout << "i=" << i << " d=" << d << " b=" << b << '\n';
}

int fi() { std::cout << "int\n"; return 1; }
double fd() { std::cout << "double\n"; return 2.1; }
bool fb() { std::cout << "bool\n"; return true; }

int main()
{
    std::cout << std::boolalpha;
    function_apply(&f, std::tuple<int, double, bool>{ fi(), fd(), fb() });
}

主要缺点是这种方法需要std::tuple<...>' 元素的规范。另一个问题是 MacOS 上当前版本的 gcc 以与它们出现的相反顺序调用函数,即不遵守括号初始化列表中的评估顺序(gcc 错误)或不存在(即,我误解了使用braced-init-list的保证。clang在同一平台上按预期顺序执行功能。

used 函数make_indices()只是创建一个合适的指针,指向一个类型的对象,indices<I...>其中包含一个可用于 a 的索引列表std::tuple<...>

template <int... Indices> struct indices;
template <> struct indices<-1> { typedef indices<> type; };
template <int... Indices>
struct indices<0, Indices...>
{
    typedef indices<0, Indices...> type;
};
template <int Index, int... Indices>
struct indices<Index, Indices...>
{
    typedef typename indices<Index - 1, Index, Indices...>::type type;
};

template <typename T>
typename indices<std::tuple_size<T>::value - 1>::type const*
make_indices()
{
    return 0;
}
于 2012-12-27T18:02:14.077 回答
1

首先,我认为如果顺序确实很重要,最好在调用之前显式构造这些元素,然后将它们传入。更容易阅读,但更不有趣!

这只是对 Kerrek 的回答的扩展:

#include <utility>

namespace detail
{
    // the ultimate end result of the call;
    // replaceable with std::result_of? I do not know.
    template <typename F, typename... Args>
    static auto ordered_call_result(F&& f, Args&&... args)
        -> decltype(std::forward<F>(f)
                    (std::forward<Args>(args)...)); // not defined

    template <typename R>
    class ordered_call_helper
    {
    public:
        template <typename F, typename... Args>
        ordered_call_helper(F&& f, Args&&... args) :
        mResult(std::forward<F>(f)(std::forward<Args>(args)...))
        {}

        operator R()
        {
            return std::move(mResult);
        }

    private:
        R mResult;
    };

    template <>
    class ordered_call_helper<void>
    {
    public:
        template <typename F, typename... Args>
        ordered_call_helper(F&& f, Args&&... args)
        {
            std::forward<F>(f)(std::forward<Args>(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<r>(detail::ordered_call_helper<r>{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__)

还有一个例子:

#include <iostream>

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

最后一行是用来验证大括号初始值设定项实际上确实有效,通常情况下。

不幸的是,正如从其他答案/评论中发现的那样,GCC 没有做对,所以我无法测试我的答案。此外,MSVC Nov2012CTP 也没有正确处理(并且有一个令人讨厌的错误,扼杀了ordered_call_result†)。如果有人想用clang测试这个,那就太好了。

†对于这个特定的例子,尾随返回类型可以decltype(f(0, 0, 0))代替。

于 2012-12-27T18:59:39.527 回答
0

可以使用对构造函数的保证来构建函数调用工具(“function_apply()”),并为参数的评估提供排序保证吗?

是的,Fit库已经这样做了fit::apply_eval

 auto result = fit::apply_eval(f, [&]{ return foo() }, [&]{ return bar(); });

所以foo()会在之前调用bar()

于 2015-06-01T23:41:19.983 回答