[英]How can I do short-circuiting conditionals in a template metaprogram?
I am implementing a merge sort in template metaprogram. 我正在模板元程序中实现合并排序。 (Believe it or not, we have a real use case for this in production.)
(信不信由你,我们在生产中有一个真正的用例。)
My code is working and my tests are passing, however I realized that in the Merge
function, when I do this: 我的代码正在运行并且我的测试通过了,但是当我这样做时,我意识到在
Merge
函数中:
using type = typename std::conditional<Compare<L1, R1>::value,
...,
...>::type;
it is going to instantiate both sides of the branch, not just one side. 它将实例化分支的两侧,而不仅仅是一侧。 That's going to make the time complexity quadratic (or worse? gulp) rather than
n log n
. 这将使时间复杂度变为二次(或更糟的是gulp),而不是
n log n
。
How can I imitate the short-circuiting behavior of ternary operator ? :
如何模仿三元运算符的短路行为
? :
? :
in a template metaprogram, so that only the work of one side of the branch is done? ? :
在模板元程序中,以便仅完成分支一侧的工作?
Tragically, I cannot use C++17 if constexpr
here, which would be perfect. 可悲的是,
if constexpr
在这里if constexpr
,我将无法使用C ++ 17,这将是完美的。 It has to work in C++14, or rather, the subset of C++14 implemented by gcc-5.4
它必须在C ++ 14中工作,或更确切地说,是由
gcc-5.4
实现的C ++ 14的子集
My initial thought was to use SFINAE like so: 我最初的想法是像这样使用SFINAE:
template <typename L1, typename R1,
typename <typename, typename> typename Compare,
typename TL, typename TR,
std::enable_if_t<Compare<L1, R1>::value> * dummy = nullptr>
Concat<TypeList<L1>, Merge_s<TL, Concat<TypeList<R1>, TR>, C> merge_branch();
template <typename L1, typename R1,
typename <typename, typename> typename Compare,
typename TL, typename TR,
std::enable_if_t<!Compare<L1, R1>::value> * dummy = nullptr>
Concat<TypeList<R1>, Merge_s<Concat<TypeList<L1>, TL>, TR, C> merge_branch();
However, I'm not sure if this will actually work as intended -- when template parameter deduction fails at dummy
above, will that prevent the compiler from instantiating the return type? 但是,我不确定这是否会按照预期的方式工作-当模板参数的推导在上述
dummy
上失败时,是否会阻止编译器实例化返回类型? Should I use an extra level of indirection (would that even help?) 我是否应该使用额外级别的间接(甚至会有所帮助吗?)
It was suggested that I could use tag dispatch instead of SFINAE. 建议我可以使用标签分发代替SFINAE。
Do the template instantiations happen as a byproduct of overload resolution, or only after overload resolution is finished? 模板实例化是作为过载解析的副产品发生的,还是仅在过载解析完成后才发生?
I fear the answer is, as a byproduct of overload resolution. 我担心答案是过载解决的副产品。
Do gcc and clang bail out early from instantiating templates when parameter dummy
fails above, or would they always instantiate the return type? 当上面的参数
dummy
失败时,gcc和clang是否会从实例化模板中提早解脱,还是它们总是实例化返回类型?
Here's my MVCE: 这是我的MVCE:
#include <cstddef>
#include <type_traits>
#include <utility>
template <typename ... Ts>
struct TypeList {
static constexpr size_t size = sizeof...(Ts);
};
// Metafunction First: Get first type from a typelist
template<typename T>
struct First_s;
template<typename T, typename... TL>
struct First_s <TypeList<T, TL...>> {
using type = T;
};
template<typename T>
using First = typename First_s<T>::type;
// Metafunction Concat: Concatenate two typelists
template<typename L, typename R>
struct Concat_s;
template<typename... TL, typename... TR>
struct Concat_s <TypeList<TL...>, TypeList<TR...>> {
using type = TypeList<TL..., TR...>;
};
template<typename L, typename R>
using Concat = typename Concat_s<L,R>::type;
// Metafunction Split: Split a typelist at a particular index
template<int i, typename TL>
struct Split;
template<int k, typename... TL>
struct Split<k, TypeList<TL...>> {
private:
using FirstSplit = Split<k/2, TypeList<TL...>>;
using SecondSplit = Split<k-k/2, typename FirstSplit::R>;
public:
using L = Concat<typename FirstSplit::L, typename SecondSplit::L>;
using R = typename SecondSplit::R;
};
template<typename T, typename... TL>
struct Split<0, TypeList<T, TL...>> {
using L = TypeList<>;
using R = TypeList<T, TL...>;
};
template<typename T, typename... TL>
struct Split<1, TypeList<T, TL...>> {
using L = TypeList<T>;
using R = TypeList<TL...>;
};
template<int k>
struct Split<k, TypeList<>> {
using L = TypeList<>;
using R = TypeList<>;
};
// Metafunction Subdivide: Split a typelist into two roughly equal typelists
template<typename TL>
struct Subdivide : Split<TL::size / 2, TL> {};
// Metafunction Reverse: Reverse a typelist
template <typename TL>
struct Reverse_s {
using S = Subdivide<TL>;
using type = Concat<typename Reverse_s<typename S::R>::type,
typename Reverse_s<typename S::L>::type>;
};
template <typename T>
struct Reverse_s<TypeList<T>> {
using type = TypeList<T>;
};
template <>
struct Reverse_s<TypeList<>> {
using type = TypeList<>;
};
template <typename TL>
using Reverse = typename Reverse_s<TL>::type;
// Metafunction MergeSort: Mergesort a typelist, using a comparator C
// Merge takes two type lists, and a comparator metafunction.
// The comparator should take two type parameters and declare `static constexpr bool value = ...`
template <typename TL, typename TR, template <typename, typename> class C>
struct Merge_s;
// TODO: Use SFINAE for the branch here because std::conditional does not short circuit :(
/*
template <typename L1, typename R1, typename <typename, typename> typename C, typename TL, typename TR, std::enable_if_t<C<L1, R1>::value> * dummy = nullptr>
Concat<TypeList<L1>, Merge_s<TL, Concat<TypeList<R1>, TR>, C> merge_branch();
template <typename L1, typename R1, typename <typename, typename> typename C, typename TL, typename TR, std::enable_if_t<!C<L1, R1>::value> * dummy = nullptr>
Concat<TypeList<R1>, Merge_s<Concat<TypeList<L1>, TL>, TR, C> merge_branch();
*/
template <template <typename, typename> class C>
struct Merge_s<TypeList<>, TypeList<>, C> {
using type = TypeList<>;
};
template <typename L1, typename ... Ls, template <typename, typename> class C>
struct Merge_s<TypeList<L1, Ls...>, TypeList<>, C> {
using type = TypeList<L1, Ls...>;
};
template <typename R1, typename ... Rs, template <typename, typename> class C>
struct Merge_s<TypeList<>, TypeList<R1, Rs...>, C> {
using type = TypeList<R1, Rs...>;
};
template <typename L1, typename R1, template <typename, typename> class C, typename TL, typename TR>
using merge_branch = typename std::conditional<C<L1, R1>::value,
Concat<TypeList<L1>, typename Merge_s<TL, Concat<TypeList<R1>, TR>, C>::type>,
Concat<TypeList<R1>, typename Merge_s<Concat<TypeList<L1>, TL>, TR, C>::type>>::type;
template <typename L1, typename... Ls, typename R1, typename ... Rs, template <typename, typename> class C>
struct Merge_s<TypeList<L1, Ls...>, TypeList<R1, Rs...>, C> {
using type = merge_branch<L1, R1, C, TypeList<Ls...>, TypeList<Rs...>>;
};
template <typename TL, typename TR, template <typename, typename> class C>
using Merge = typename Merge_s<TL, TR, C>::type;
// Here is merge sort
template <typename T, template <typename, typename> class C>
struct MergeSort_s;
template <template <typename, typename> class C>
struct MergeSort_s<TypeList<>, C> {
using type = TypeList<>;
};
template <typename T, template <typename, typename> class C>
struct MergeSort_s<TypeList<T>, C> {
using type = TypeList<T>;
};
template <typename T, typename... Ts, template <typename, typename> class C>
struct MergeSort_s <TypeList<T, Ts...>, C>{
using S = Subdivide<TypeList<T, Ts...>>;
using L = typename MergeSort_s<typename S::L, C>::type;
using R = typename MergeSort_s<typename S::R, C>::type;
using type = Merge<L, R, C>;
};
template <typename T, template <typename, typename> class C>
using MergeSort = typename MergeSort_s<T, C>::type;
// Tests
struct A{};
struct B{};
struct C{};
// Concat tests
static_assert(std::is_same<TypeList<A, B, C>, //
Concat<TypeList<>, TypeList<A, B, C>>>::value, ""); //
static_assert(std::is_same<TypeList<A, B, C>, //
Concat<TypeList<A>, TypeList<B, C>>>::value, ""); //
static_assert(std::is_same<TypeList<A, B, C>, //
Concat<TypeList<A, B>, TypeList<C>>>::value, ""); //
static_assert(std::is_same<TypeList<A, B, C>, //
Concat<TypeList<A, B, C>, TypeList<>>>::value, ""); //
// Split tests
static_assert(std::is_same<TypeList<A>, //
typename Split<1, TypeList<A, B, C>>::L>::value, ""); //
static_assert(std::is_same<TypeList<B, C>, //
typename Split<1, TypeList<A, B, C>>::R>::value, ""); //
static_assert(std::is_same<TypeList<A, B>, //
typename Split<2, TypeList<A, B, C>>::L>::value, ""); //
static_assert(std::is_same<TypeList<C>, //
typename Split<2, TypeList<A, B, C>>::R>::value, ""); //
// Reverse tests
static_assert(std::is_same<TypeList<B, A>, //
Reverse<TypeList<A, B>>>::value, ""); //
static_assert(std::is_same<TypeList<C, B, A>,//
Reverse<TypeList<A, B, C>>>::value, ""); //
// Sorting tests
template <typename T1, typename T2>
struct IntCmp;
template <int a, int b>
struct IntCmp<std::integral_constant<int, a>, std::integral_constant<int, b>> {
static constexpr bool value = (a < b);
};
template <int x>
using IntC = std::integral_constant<int, x>;
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>>, //
MergeSort<TypeList<IntC<1>, IntC<2>>, IntCmp>>::value, ""); //
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>>,//
MergeSort<TypeList<IntC<2>, IntC<1>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>>,//
MergeSort<TypeList<IntC<3>, IntC<1>, IntC<2>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>>,//
MergeSort<TypeList<IntC<1>, IntC<3>, IntC<2>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>>,//
MergeSort<TypeList<IntC<2>, IntC<3>, IntC<1>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>>,//
MergeSort<TypeList<IntC<1>, IntC<2>, IntC<3>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>>,//
MergeSort<TypeList<IntC<2>, IntC<1>, IntC<3>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>>,//
MergeSort<TypeList<IntC<1>, IntC<2>, IntC<3>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>, IntC<4>>,//
MergeSort<TypeList<IntC<1>, IntC<2>, IntC<3>, IntC<4>>, IntCmp>>::value, "");//
static_assert(std::is_same<TypeList<IntC<1>, IntC<2>, IntC<3>, IntC<4>>,//
MergeSort<TypeList<IntC<3>, IntC<4>, IntC<2>, IntC<1>>, IntCmp>>::value, "");//
Attribution: Some of the details in the above are informed by Yakk's comments in another answer 来源: Yakk在另一个答案中的评论提供了上述某些详细信息
I propose to pass trough an helper struct with partial specialization 我建议通过部分专业化的帮助结构
template <typename L1, typename R1, template <typename, typename> class C,
typename TL, typename TR, bool = C<L1, R1>::value>
struct merge_branch_h
{ using type = Concat<TypeList<L1>,
typename Merge_s<TL, Concat<TypeList<R1>, TR>, C>::type>; };
template <typename L1, typename R1, template <typename, typename> class C,
typename TL, typename TR>
struct merge_branch_h<L1, R1, C, TL, TR, false>
{ using type = Concat<TypeList<R1>,
typename Merge_s<Concat<TypeList<L1>, TL>, TR, C>::type>; };
template <typename L1, typename R1, template <typename, typename> class C,
typename TL, typename TR>
using merge_branch = typename merge_branch_h<L1, R1, C, TL, TR>::type;
Add an extra layer of indirection. 添加额外的间接层。 Boost.MPL had this metafunction called
eval_if
which is like conditional
except instead of taking two types, it takes two nullary metafunctions and evaluates one or the other. Boost.MPL具有称为
eval_if
此元函数,它类似于conditional
只是不采用两种类型,而是采用了两个无效元函数并求值一个或另一个。 It's extremely easy to implement: 它非常容易实现:
template <bool B, typename T1, typename T2>
using eval_if = typename std::conditional<B, T1, T2>::type::type;
So let's add a metafunction do your concatenation/merge: 因此,让我们添加一个元函数来进行串联/合并:
template <typename T>
struct identity {
using type = T;
};
template <typename L, typename R>
struct delay_concat {
using type = Concat<typename L::type, typename R::type>;
};
And then you can swap your: 然后,您可以交换您的:
typename std::conditional<C<L1, R1>::value,
Concat<TypeList<L1>, typename Merge_s<TL, Concat<TypeList<R1>, TR>, C>::type>,
Concat<TypeList<R1>, typename Merge_s<Concat<TypeList<L1>, TL>, TR, C>::type>
with: 有:
eval_if<C<L1, R1>::value,
delay_concat<identity<TypeList<L1>>, Merge_s<TL, Concat<TypeList<R1>, TR>, C>>,
delay_concat<identity<TypeList<R1>>, Merge_s<Concat<TypeList<L1>, TL>, TR, C>>>
which does short circuit. 会短路。
Which should probably generalize out to: 可能应该概括为:
template <template <typename...> class Z, typename... Ts>
struct delay_eval {
using type = Z<typename Ts::type...>;
};
And then make TypeList
a metafunction that yields itself, so we don't have to wrap them in identity
. 然后将
TypeList
一个产生自身的元函数,因此我们不必将它们包装在identity
。 This allows: 这允许:
eval_if<C<L1, R1>::value,
delay_eval<Concat, TypeList<L1>, delay_eval<Merge_s, TL, delay_eval<Concat, TypeList<R1>, TR>, C>>,
delay_eval<Concat, TypeList<R1>, delay_eval<Merge_s, delay_eval<Concat, TypeList<L1>, TL>, TR, C>>>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.