当在参数包上递归时,给定选择,我是否应该通过继承或通过成员字段(组合)来递减?这是一种干燥的物质吗?有什么权衡取舍?
我想知道的一件事是,基于组合的表单通常被认为具有更好的编译速度,内存使用情况或错误报告。
为了说明,以下是Jonathan Wakely的回答here启发的短路or_
(分离)的例子。
继承基于
#include <type_traits>
// disjunction
template<typename... Conds>
struct or_ : std::false_type {};
template<typename Cond, typename... Conds>
struct or_<Cond, Conds...>
: std::conditional<Cond::value, std::true_type, or_<Conds...>>::type
{};
static_assert(or_<std::true_type, std::true_type, std::true_type>::value,"");
static_assert(or_<std::false_type, std::false_type, std::true_type>::value,"");
static_assert(or_<std::false_type, std::false_type, std::false_type>::value == false,"");
我了解此版本具有or_<Ts...>
将从std::integral_constant
继承的功能。为了我的问题,请假设我不关心or_
是否继承自integral_constant
。
组合物为基础的:
template<typename... Conds>
struct or_ {
static constexpr bool value = false;
};
template<typename Cond, typename... Conds>
struct or_<Cond, Conds...> {
static constexpr bool value = std::conditional<Cond::value, std::true_type, or_<Conds...>>::type::value;
};
这个形式对我来说似乎更直观,因为值总是位于类型本身,而不是某些超类,但我不确定这通常被认为是更好的。
P.S。我知道在C ++ 17中我经常使用折叠表达式。但我的目标是兼容C ++ 11。
答案 0 :(得分:3)
如果我们真的不关心短路,那么总是bool_pack
:
template<bool...> struct bool_pack;
template<class... Ts>
using and_ = typename std::is_same<bool_pack<true, Ts::value...>,
bool_pack<Ts::value..., true>>::type;
template<class T>
using not_ = std::integral_constant<bool, !T::value>;
template<class... Ts>
using or_ = not_<std::is_same<bool_pack<false, Ts::value...>,
bool_pack<Ts::value..., false>>>;
// or not_<and_<not_<Ts>...>>
答案 1 :(得分:2)
两种形式都会产生类似的效果。如果你想让代码变得更好而不是在这两者之间进行选择,你可以尝试避免递归......你的or_
代码可以成功地替换为以下代码:
#include <utility>
#include <type_traits>
#include <iostream>
template <std::size_t, class T>
struct indexed_condition;
template <std::size_t I>
struct indexed_condition<I, std::false_type> { };
template <class...>
struct voider {
using type = void;
};
template <class... Conds>
struct helper: std::true_type {
};
template <std::size_t... Is, class... Conds>
struct helper<typename voider<decltype(indexed_condition<Is, Conds>())...>::type, std::index_sequence<Is...>, Conds...>: std::false_type {
};
template <class... Conds>
struct my_or: helper<void, std::make_index_sequence<sizeof...(Conds)>, Conds...> { };
int main() {
std::cout << my_or<std::true_type, std::false_type, std::true_type>::value << std::endl;
std::cout << my_or<std::false_type, std::false_type, std::false_type>::value << std::endl;
}
输出:
1
0
直接它不会影响运行时间,但会影响编译时效率。
还有一件事 - std::integer_sequence
是c ++ 14构造,但也可以在c ++ 11中实现,例如this implementation
答案 2 :(得分:2)
这是试图制作一个稍微清洁的@ W.F.的答案。
template<std::size_t, class T, class=void>
struct detect_truthy : virtual std::false_type {};
template<std::size_t I, class T>
struct detect_truthy<I, T,
typename std::enable_if<T::value>::type
> : virtual std::true_type {};
template<class...>struct pack {};
template<class Pack, class Is=void>
struct detect_truthies;
template<class...Ts>
struct detect_truthies<pack<Ts...>,void>:
detect_truthies<pack<Ts...>, std::make_index_sequence<sizeof...(Ts)>>
{};
template<class...Ts, std::size_t...Is>
struct detect_truthies<pack<Ts...>,std::index_sequence<Is...>>:
detect_truthy<Is, Ts>...
{};
std::true_type or_helper_f( std::true_type* );
std::false_type or_helper_f( ... );
std::false_type and_helper_f( std::false_type* );
std::true_type and_helper_f( ... );
template<class...Bools>
struct my_or:
decltype( or_helper_f( (detect_truthies<pack<Bools...>>*)0 ) )
{};
template<class...Bools>
struct my_and:
decltype( and_helper_f( (detect_truthies<pack<Bools...>>*)0 ) )
{};
它使用来自C ++ 14的index_sequence
和make_index_sequence
,但可以使用高质量的C ++ 11版本。
这种技术的意义在于index_sequence
内只有递归/线性/二进制模板扩展。我们在其他地方直接扩展参数包。
由于index_sequence
可以写成高效率,但代价是可读性(例如,MSVC将其作为内在函数实现!其他编译器使用复杂的二叉树生成器。),这关注高成本性能问题那里。
通常,您希望隔离在编译时执行O(N ^ 2)工作的位置。当你线性继承时,你做O(N ^ 2)工作。
答案 3 :(得分:1)
第三种选择是使用constexpr功能
static constexpr bool any_of(){
return false;
}
template<class... T>
static constexpr bool any_of(bool b, T... rest){
return b || any_of(rest...);
}
template<class... Cond>
using or_ = std::integral_constant<bool, any_of(Cond::value...)>;
int main(){
static_assert(or_<std::true_type, std::true_type, std::true_type>::value,"");
static_assert(or_<std::false_type, std::false_type, std::true_type>::value,"");
static_assert(or_<std::false_type, std::false_type, std::false_type>::value == false,"");
}
继承方法的优点是你可以将_or类传递给任何使用std :: true_type和std :: false_type重载的函数,如果你正在玩sfinae,这会有所帮助。
就编译速度而言,它很难分辨,因为它完全依赖于编译器(并且编译器人正在为TMP编写速度,所以任何好的建议现在都可能变成糟糕的建议而没有警告。
编译速度最重要的是要密切关注你的算法。在C ++ 11/14中进行条件编译非常困难,编译器将评估std :: conditional的两侧。 (他们必须合法地专门为其中一个_或类提供std :: conditional,并且编译器无法证明你是一个理性的人类)
这意味着在两种情况下,编译器都会为每个部分案例列表实例化或类。这可能不是太糟糕,但有时您可以突然将O(N)算法转换为O(2 ^ N)算法,这时编译时间真的开始受到伤害。
答案 4 :(得分:0)
在or_
编译时间方法的上下文中,效率更高:
#include <type_traits>
#include <utility>
template <class... Conds>
std::false_type or_impl(Conds... conds);
template <class... Conds>
std::true_type or_impl(...);
template <class, class T>
using typer = T;
template <class... Conds>
using or_ = decltype(or_impl<typer<Conds, std::false_type>...>(Conds{}...));
int main() {
static_assert(or_<std::false_type, std::true_type, std::false_type>::value, "!");
static_assert(!or_<std::false_type, std::false_type, std::false_type>::value, "!");
}