我遇到了重新排序提供给struct的构造函数的可变参数列表的需要。根据类型重新排序后,参数将存储为元组。我的问题是如何做到这一点,以便现代C ++编译器(例如g++-4.7
)不会生成不必要的加载或存储指令。也就是说,当使用可变大小的参数列表调用构造函数时,它会根据参数类型的排序有效地将每个参数推送到位。
这是一个具体的例子。假设每个参数的基本类型(没有引用,右值引用,指针或限定符)是char
,int
或float
。我怎样才能使基本类型char
的所有参数首先出现,然后是基类型int
的所有参数(最后保留基类型float
的参数)。在同质基类型的子列表中不应违反给出参数的相对顺序。
示例:使用参数foo::foo()
调用float a, char&& b, const float& c, int&& d, char e
。元组tupe是std::tuple<char, char, int, float, float>
,它的构造如下:tuple_type{std::move(b), e, std::move(d), a, c}
。
考虑下面定义的结构,并假设已经实现了元函数deduce_reordered_tuple_type
。你会如何编写构造函数以使其按预期工作?如果您认为deduce_reodered_tuple_type
的代码对您有用,我可以提供它;它有点长。
template <class... Args> struct foo
{
// Assume that the metafunction deduce_reordered_tuple_type is defined.
typedef typename deduce_reordered_tuple_type<Args...>::type tuple_type;
tuple_type t_;
foo(Args&&... args) : t_{reorder_and_forward_parameters<Args>(args)...} {}
};
修改1 我上面描述的技术确实在数学框架中有应用程序,这些应用程序大量使用表达式模板,可变参数模板和元编程,以便执行积极的内联。假设您希望定义一个运算符,该运算符采用多个表达式的乘积,每个表达式可以通过引用传递,引用const或rvalue引用。 (在我的例子中,表达式是条件概率表,操作是因子乘积,但矩阵乘法之类的东西也适用。)
您需要访问每个表达式提供的数据才能评估产品。因此,您必须移动作为rvalue引用传递的表达式,复制通过引用传递给const的表达式,并获取通过引用传递的表达式的地址。使用我上面描述的技术现在有几个好处。
非常感谢你的帮助!
答案 0 :(得分:4)
事实证明,g++-4.7
在使用中非常棒,并根据我的测试创建相同的机器代码(重现结果的说明在代码下方)。
#include <iostream>
#include <tuple>
#include <typeinfo>
#include <type_traits>
template <class... Args>
struct sequence
{
typedef std::tuple<Args...> tuple_type;
};
template <class U, class V>
struct sequence_cat;
template <class... U, class... V>
struct sequence_cat<sequence<U...>, sequence<V...>>
{
typedef sequence<U..., V...> type;
};
template <class... V>
struct sequence_cat<void, sequence<V...>>
{
typedef sequence<V...> type;
};
template <class... U>
struct sequence_cat<sequence<U...>, void>
{
typedef sequence<U...> type;
};
template <>
struct sequence_cat<void, void>
{
typedef void type;
};
template <class T>
struct undecorate
{
typedef typename std::remove_reference<T>::type noref_type;
typedef typename std::remove_pointer<noref_type>::type noptr_type;
typedef typename std::remove_cv<noptr_type>::type type;
};
template <class T>
struct deduce_storage_type
{
typedef T type;
};
template <class T>
struct deduce_storage_type<T&>
{
typedef T* type;
};
template <class T>
struct deduce_storage_type<const T&>
{
typedef T type;
};
template <class T>
struct deduce_storage_type<T&&>
{
typedef T type;
};
template <class T, class... Args>
struct filter_type;
template <class T, class Arg, class... Args>
struct filter_type<T, Arg, Args...>
{
static constexpr bool pred =
std::is_same<typename undecorate<Arg>::type, T>::value;
typedef typename deduce_storage_type<Arg>::type storage_type;
typedef typename
std::conditional<
pred,
typename sequence_cat<
sequence<storage_type>,
typename filter_type<T, Args...>::type
>::type,
typename filter_type<T, Args...>::type
>::type type;
};
template <class T, class Arg>
struct filter_type<T, Arg>
{
static constexpr bool pred =
std::is_same<typename undecorate<Arg>::type, T>::value;
typedef typename deduce_storage_type<Arg>::type storage_type;
typedef typename
std::conditional<pred, sequence<storage_type>, void>::type
type;
};
template <class... Args>
struct deduce_sequence_type
{
typedef typename filter_type<char, Args...>::type char_sequence;
typedef typename filter_type<int, Args...>::type int_sequence;
typedef typename filter_type<float, Args...>::type float_sequence;
typedef typename
sequence_cat<
char_sequence,
typename sequence_cat<
int_sequence,
float_sequence
>::type
>::type type;
};
template <class T>
struct get_storage_type
{
static T apply(T t) { return t; }
};
template <class T>
struct get_storage_type<T&>
{
static T* apply(T& t) { return &t; }
};
template <class T>
struct get_storage_type<const T&>
{
static T apply(const T& t) { return t; }
};
template <class T>
struct get_storage_type<T&&>
{
static T&& apply(T&& t) { return std::move(t); }
};
template <class T, class Arg>
struct equals_undecorated_type
{
static constexpr bool value =
std::is_same<typename undecorate<Arg>::type, T>::value;
};
template <bool Pred, bool IsNextVoid, class T, class... Args>
struct filter_parameter_impl;
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<false, false, T, Arg1, Arg2, Args...>
{
typedef typename filter_type<T, Arg2, Args...>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static constexpr bool pred = equals_undecorated_type<T, Arg2>::value;
static constexpr bool is_next_next_void =
std::is_same<typename filter_type<T, Args...>::type, void>::value;
static tuple_type apply(Arg1&&, Arg2&& arg2, Args&&... args)
{
return filter_parameter_impl<
pred, is_next_next_void, T, Arg2, Args...
>::apply(
std::forward<Arg2>(arg2),
std::forward<Args>(args)...
);
}
};
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<false, true, T, Arg1, Arg2, Args...>
{
static void apply(Arg1&&, Arg2&&, Args&&...) {}
};
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<true, false, T, Arg1, Arg2, Args...>
{
typedef typename
filter_type<T, Arg1, Arg2, Args...>::type
sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static constexpr bool pred = equals_undecorated_type<T, Arg2>::value;
static constexpr bool is_next_next_void =
std::is_same<typename filter_type<T, Args...>::type, void>::value;
static tuple_type apply(Arg1&& arg1, Arg2&& arg2, Args&&... args)
{
return std::tuple_cat(
std::make_tuple(get_storage_type<Arg1>::apply(arg1)),
filter_parameter_impl<
pred, is_next_next_void, T, Arg2, Args...
>::apply(
std::forward<Arg2>(arg2),
std::forward<Args>(args)...
)
);
}
};
template <class T, class Arg1, class Arg2, class... Args>
struct filter_parameter_impl<true, true, T, Arg1, Arg2, Args...>
{
typedef typename filter_type<T, Arg1>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&& arg1, Arg2&&, Args&&...)
{
return std::make_tuple(get_storage_type<Arg1>::apply(
std::forward<Arg1>(arg1)
));
}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<false, false, T, Arg1, Arg2>
{
typedef typename filter_type<T, Arg2>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&&, Arg2&& arg2)
{
return std::make_tuple(get_storage_type<Arg2>::apply(
std::forward<Arg2>(arg2)
));
}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<false, true, T, Arg1, Arg2>
{
static void apply(Arg1&&, Arg2&&) {}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<true, false, T, Arg1, Arg2>
{
typedef typename filter_type<T, Arg1>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&& arg1, Arg2&& arg2)
{
return std::make_tuple(
get_storage_type<Arg1>::apply(std::forward<Arg1>(arg1)),
get_storage_type<Arg2>::apply(std::forward<Arg2>(arg2))
);
}
};
template <class T, class Arg1, class Arg2>
struct filter_parameter_impl<true, true, T, Arg1, Arg2>
{
typedef typename filter_type<T, Arg1, Arg2>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Arg1&& arg1, Arg2&&)
{
return std::make_tuple(
get_storage_type<Arg1>::apply(std::forward<Arg1>(arg1))
);
}
};
template <class T, class... Args>
struct filter_parameter;
template <class T, class Arg, class... Args>
struct filter_parameter<T, Arg, Args...>
{
typedef typename filter_type<T, Arg, Args...>::type sequence_type;
typedef typename std::conditional<
std::is_same<sequence_type, void>::value,
void,
typename sequence_type::tuple_type
>::type tuple_type;
static constexpr bool pred = equals_undecorated_type<T, Arg>::value;
static constexpr bool is_next_void =
std::is_same<typename filter_type<T, Args...>::type, void>::value;
static tuple_type apply(Arg&& arg, Args&&... args)
{
return filter_parameter_impl<
pred, is_next_void, T, Arg, Args...
>::apply(std::forward<Arg>(arg), std::forward<Args>(args)...);
}
};
template <bool Is1Void, bool Is2Void, bool Is3Void, class... Args>
struct get_tuple_impl;
template <class... Args>
struct get_tuple_impl<false, false, false, Args...>
{
typedef typename deduce_sequence_type<Args...>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
static tuple_type apply(Args&&... args)
{
return std::tuple_cat(
filter_parameter<char, Args...>::apply(
std::forward<Args>(args)...
),
filter_parameter<int, Args...>::apply(
std::forward<Args>(args)...
),
filter_parameter<float, Args...>::apply(
std::forward<Args>(args)...
)
);
}
};
template <class... Args>
struct get_tuple
{
typedef typename deduce_sequence_type<Args...>::type sequence_type;
typedef typename std::conditional<
std::is_same<sequence_type, void>::value,
void,
typename sequence_type::tuple_type
>::type tuple_type;
static constexpr bool is1void =
std::is_same<typename filter_type<char, Args...>::type, void>::value;
static constexpr bool is2void =
std::is_same<typename filter_type<int, Args...>::type, void>::value;
static constexpr bool is3void =
std::is_same<typename filter_type<float, Args...>::type, void>::value;
static tuple_type apply(Args&&... args)
{
return get_tuple_impl<is1void, is2void, is3void, Args...>::
apply(std::forward<Args>(args)...);
}
};
template <class... Args>
struct foo
{
typedef typename deduce_sequence_type<Args...>::type sequence_type;
typedef typename sequence_type::tuple_type tuple_type;
tuple_type t_;
foo(Args&&... args) : t_{} {}
};
int main()
{
char a = 5;
const int b = 6;
float c = 7;
int d = 8;
float e = 9;
char f = 10;
auto x = get_tuple<char&, const int&, float&, int&, float&&, char&>::
apply(a, b, c, d, std::move(e), f);
//std::tuple<char*, char*, int, int*, float*, float> x{&a, &f, b, &d, &c, std::move(f)};
std::cout << typeid(x).name() << std::endl;
return 0;
}
为了重现结果,请执行以下操作(假设您已安装g ++ - 4.7)。
g++ -std=c++11 -Wall -Ofast -march=native variadic_reorder.cpp -S -o with_templates.s
// Comment out the line in main, and comment the line containing auto x, as well as the line below it.
g++ -std=c++11 -Wall -Ofast -march=native variadic_reorder.cpp -S -o without_templates.s
vimdiff with_templates.s without_templates.s
我注意到的唯一不同之处就是标签的名称;否则,生成的机器代码是相同的。
修改1 我会接受自己的答案,直到别人发布比我更优雅的东西。
答案 1 :(得分:1)
我没时间试验这个,但如果你的编译器在置换转发的参数时产生了太多的动作,我建议转发std::forward_as_tuple
。元组是一个具有混凝土布局的数据结构,构造它可以鼓励编译器按照你想要的顺序将所有内容放入内存中。
另一方面,它不太可能提升寄存器的参数并绕过堆栈以获得简单的函数。只要元组仅用作纯值(不参考),就没有任何保证,因为在as-if规则下,其成员不需要任何地址。
我们可以通过将指针组合在一起并将更大的表达式存储到元组的末尾来节省堆栈空间。
Lvalue引用由ABI实现为指针,但您指定将它们分组为数据值。如果要遵循本机传递语义,则应将Rvalue引用视为与左值引用相同。 (我假设你只会移动类类型。)所以排序问题比声明的要复杂一些,因为你想要将参数排序为值和指针类别,然后按基类型对它们进行排序。
至于排序算法本身,我会尝试从输入包中弹出并推送到一组输出元组,队列样式,然后用std::tuple_cat
连接输出元组。这将是最简单的实现,稳定,并应该击中编译器的常见情况优化。不要实现一个旨在在内存中就地运行的算法,因为TMP不能像那样工作。
至于将排序结果转换为将参数置换为forward_as_tuple
的参数的函数,我不太确定。你可能不得不处理索引。
在承诺这一切之前,你要非常肯定这个好处是值得的。