假设我们有类型的线性层次结构,如下所示:
然后我想要的是一种机制,用于返回该谱系中任意数量类型的最低共同祖先。
template<typename...Ts>
struct LCA;
template<typename T1, typename T2, typename...Ts>
struct LCA<T1, T2, Ts...>
{
using base = typename std::conditional
<
std::is_base_of<T1, T2>::value, T1,
typename std::conditional <
std::is_base_of<T2, T1>::value, T2, void
>::type
>::type;
using type = typename LCA<base, Ts...>::type;
};
template<typename T>
struct LCA<T>
{
using type = T;
};
我的用例很典型:在制作一些iterator
工具时,我想提取“限制性最强”的迭代器类型,所以因为在迭代器中存在(种类)线性层次结构,我应该能够提升层次结构尽可能多:
LCA<Bidirectional, RandomAccess, RandomAccess> -> Bidirectional
LCA<RandomAccess, Input, Forward> -> Input
是否有更简洁/惯用的处理错误案例的方式,其中两个或更多类型是层次结构的陌生人?当前的方法是返回void
,这有望在实际使用类型的大多数情况下失败。
在第一个专业化中使用额外的base
成员是否有问题?我应该在单独的类中提取该功能并在type
中内联使用保持一致性吗?
是否存在减少实例化数量的算法?有没有比成对比较更好的方法,这样可以降低算法的复杂性?
任何人都可以扩展到非线性层次结构并深入查询层次结构树吗?在这种情况下,对于同一级别的类型,什么是好的“打破”?
答案 0 :(得分:14)
我使用派生,因为这比类型定义更清晰。以下是一些示例代码:
#include <iostream>
#include <typeinfo>
#include <type_traits>
struct Grandma {};
struct Mom : Grandma {};
struct Daughter : Mom {};
struct Son : Mom {};
struct Grandchild : Son {};
struct Stranger {};
namespace detail
{
struct TypeIsNotPartOfTheHierarchy {};
template<typename T>
struct TypeWrapper
{
static_assert(!std::is_same<TypeIsNotPartOfTheHierarchy, T>::value,
"using types of different type hierarchies.");
using type = T;
};
}
template<typename... Ts>
struct LCA;
template<typename T>
struct LCA<T>: detail::TypeWrapper<T>
{};
template<typename T1, typename T2>
struct LCA<T1, T2>:
std::conditional
<
std::is_base_of<T1, T2>::value,
detail::TypeWrapper<T1>,
typename std::conditional
<
std::is_base_of<T2, T1>::value,
detail::TypeWrapper<T2>,
detail::TypeWrapper<detail::TypeIsNotPartOfTheHierarchy>
>::type
>::type
{};
template<typename T1, typename... Ts>
struct LCA<T1, Ts...>: LCA<T1, typename LCA<Ts...>::type>
{};
int main()
{
std::cout << typeid(LCA<Son, Mom, Grandchild, Grandma, Son, Son>::type).name() << std::endl;
std::cout << typeid(LCA<Son>::type).name() << std::endl;
// error because Daughter and Son are siblings.
// std::cout << typeid(LCA<Son, Daughter, Son>::type).name() << std::endl;
// error because Son is not related to the Stranger.
// std::cout << typeid(LCA<Son, Stranger, Son>::type).name() << std::endl;
return 0;
}
从技术上讲,您可以使用std::enable_if
代替std::condition
,但使用std::enable_if
意味着您必须从if-true,if-false和if-types-not派生兼容的情况。使用std::condition
是恕我直言,更具可读性。该类型必须再次包装以具有可由条件启用的类型,然后提供typedef以在外部使用它。
为了获得编译错误,静态断言它会给你一个很好的消息,而不是编译器输出中的困难模板错误。然后您可以自由使用void
来表示错误。我建议使用额外的类型来命名此错误。这也提高了可读性。
我认为base
成员应该被隐藏,因为否则你会向用户透露超过需要的内容,这可能会让他们感到困惑。类型派生的使用解决了这个问题。
我认为,不可能提高复杂度O(n)。如果每种类型可能是LCA
类型,则必须至少检查一次。所以每种类型至少都是比较的一部分。
上面的实现(正如你的那样)对其他层次结构没有任何意义,而不是线性的(例如LCA<Daughter, Grandma, Son>
将返回Grandma
,而LCA<Grandma, Daughter, Son>::type
将导致错误,因为只有邻居类型进行比较)。
然而,有两种类型的&#34;分支继承&#34;在C ++中可能(并且当然要混合它):
struct Dad {};
struct Mom {};
struct Son: Dad, Mom {};
在某些情况下LCA
未定义(例如LCA<Mom, Dad>::type
我想,妈妈和爸爸不共享同一个父母)。所以我建议放弃这个案例。
struct Mom {};
struct Son: Mom {};
struct Daughter: Mom {};
我建议,算法只返回一个类型,如果列表中有一个类型,所有类型都可以被转换为(例如LCA<Son, Daughter>::type
没有LCA,因为我希望它们是兄弟姐妹)。因此,我们在列表中搜索该类型,这是所有其他类型的基本类型。
因为只有上面的邻居类型相互比较,所以必须扩展比较以将每种类型相互比较(遗憾的是这是O(n ^ 2))。所以基本的想法是检查每种类型,如果它是所有其他类型的共同祖先。这只是LCA
的情况。顺便说一句:以这种方式解决它有另一个好处,因为你会在多个根&#34; -scenario中得到一个错误,但如果多个根再次加入一个共同的根(这是一部分),那么结果是正确的列表)。
我们首先需要一个功能,它决定一种类型是否是所有其他类型的基本类型:
template<typename StillCommonAncestor, typename TypeToCheck, typename... Ts>
struct IsCommonAncestor;
template<typename StillCommonAncestor, typename TypeToCheck>
struct IsCommonAncestor<StillCommonAncestor, TypeToCheck>
{
static constexpr bool value = StillCommonAncestor::value;
};
template<typename StillCommonAncestor, typename TypeToCheck, typename T1, typename... Ts>
struct IsCommonAncestor<StillCommonAncestor, TypeToCheck, T1, Ts...>:
IsCommonAncestor
<
std::integral_constant
<
bool,
std::conditional
<
std::is_base_of<TypeToCheck, T1>::value,
std::true_type,
std::false_type
>::type::value && StillCommonAncestor::value
>,
TypeToCheck,
Ts...
>
{};
要检查某个类型是否是所有其他类型的共同祖先,只需使用IsCommonAncestor<std::true_type, Mom, Grandchild, Daughter, Son>::value
(此处为true,而IsCommonAncestor<std::true_type, Grandchild, Grandchild, Daughter, Son>::value
为false)。请注意,如果一种类型不属于类型层次结构,则该值也为false。
然后一些&#34;设施&#34;需要,遍历类型并捕获唯一的IsCommonAncestor<...>::value
为真的
template<typename Pack, typename... Ts>
struct LCA;
template<typename... PackParams, typename T1>
struct LCA<std::tuple<PackParams...>, T1>:
std::conditional
<
IsCommonAncestor<std::true_type, T1, PackParams...>::value,
TypeWrapper<T1>,
TypeWrapper<TypeIsNotPartOfTheHierarchy>
>::type
{};
template<typename... PackParams, typename T1, typename... Ts>
struct LCA<std::tuple<PackParams...>, T1, Ts...>:
std::conditional
<
IsCommonAncestor<std::true_type, T1, PackParams...>::value,
TypeWrapper<T1>,
LCA<std::tuple<PackParams...>, Ts...>
>::type
{};
LCA将每个元素与整个模板参数包进行比较。首先
这是所有使用的基本类型。如果最后一个也没有基本类型
其他人,LCA再次来自TypeWrapper<TypeIsNotPartOfTheHierarchy>
,其中
将提出典型的静态断言。
这个非常不方便。包装器将修复此问题:
template<typename... Ts>
struct LCA: detail::LCA<std::tuple<Ts...>, Ts...>
{};
此处提供了完整的树LCA代码:http://ideone.com/pYEPYl
答案 1 :(得分:3)
std::enable_if
会导致编译错误。回退到定义void类型的另一种方法是导致编译错误。
我认为如果使用显式继承,模板会更整洁,作为解析正确类型的一种方法,见下文。
我不知道如何将复杂性降低到线性复杂度之下。不知何故,在某种程度上,您必须迭代所有模板参数类型,以便选择其中一个。
std :: is_base_of并没有真正告诉你子类在超类下面的深度,只是一个是另一个的子类。另外,对于多重继承,给定的子类可能出现在超类之下的不同级别,因此语义有点混乱。
无论如何,我认为使用单独的类来执行成对类型比较,并使用继承,看起来更清晰:
template<typename T1, typename T2, bool is_t1_base, bool is_t2_base>
class which_one_is_base;
// If T1 and T2 are the same, dontcare will be true.
template<typename T1, typename T2, bool dontcare>
class which_one_is_base<T1, T2, true, dontcare> {
public:
typedef T1 type;
};
template<typename T1, typename T2>
class which_one_is_base<T1, T2, false, true> {
public:
typedef T2 type;
};
template<typename T1, typename T2>
class select_base : public which_one_is_base<T1, T2,
std::is_base_of<T1, T2>::value,
std::is_base_of<T2, T1>::value>
{
};
//////////////////////////////////////////////////////////////////////
template<typename ...Ts> class LCA;
template<typename T1> class LCA<T1> {
public:
typedef T1 type;
};
template<typename T1, typename T2, typename ...Ts>
class LCA<T1, T2, Ts...> :
public LCA< typename select_base<T1, T2>::type, Ts...> {
};