关于how to avoid undefined execution order for the constructors when using std::make_tuple问题的答案导致了一个讨论,在此期间我了解到构造函数可以保证参数评估的顺序:使用 braced-init-list 命令保证从左到右:
T{ a, b, c }
表达式a
,b
和c
按给定顺序进行评估。即使类型T
只定义了正常的构造函数,也是如此。
显然,并非所有被调用的都是构造函数,有时在调用函数时保证求值顺序会很好,但是没有 brace-argument-list 来调用函数他们的论点的定义评估顺序。问题变成:构造函数的保证是否可以用于构建函数调用工具(“function_apply()
”),并具有用于评估参数的排序保证?要求调用函数对象是可以接受的。
答案 0 :(得分:14)
这样一个愚蠢的包装类怎么样:
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();
答案 1 :(得分: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以与它们出现的相反顺序调用函数,即,不遵守 braced-init-list 中的评估顺序(gcc bug)或者不存在(即,我误解了使用braced-init-list的保证。同一平台上的clang以预期的顺序执行函数。
使用的函数make_indices()
只是创建一个指向indices<I...>
类型对象的合适指针,其中包含可与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;
}
答案 2 :(得分: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))
。
答案 3 :(得分:0)
构造函数的保证是否可以用于构建函数调用工具(&#34; function_apply()&#34;),并具有用于评估参数的排序保证?
是的,Fit库已经使用fit::apply_eval
执行此操作:
auto result = fit::apply_eval(f, [&]{ return foo() }, [&]{ return bar(); });
因此foo()
之前会调用bar()
。