在参数包上递归时的继承或组合?

时间:2016-12-23 12:04:20

标签: c++ c++11 templates

当在参数包上递归时,给定选择,我是否应该通过继承或通过成员字段(组合)来递减?这是一种干燥的物质吗?有什么权衡取舍?

我想知道的一件事是,基于组合的表单通常被认为具有更好的编译速度,内存使用情况或错误报告。

为了说明,以下是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。

5 个答案:

答案 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;
}

[live demo]

输出:

  

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

live example

它使用来自C ++ 14的index_sequencemake_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, "!");
}

[live demo]