在 C++ 中推导两个 类 的共享基数
Deducing a shared base of two classes in C++
我几乎可以肯定,如果没有 反思,我正在寻找的东西就无法完成,这还没有出现在语言中。但偶尔我会对 SO 中的特殊答案感到惊讶,所以让我们试试吧。
是否可以推导出具有共同共享基class的两种类型的“common_base”,所以下面是可能的(伪代码!--语言中没有 common_base_t
,这就是我要实现的魔法):
template<typename T1, typename T2>
const common_base_t<T1, T2>& foo(const T1& a1, const T2& a2) {
if(a1 < a2) return a1;
return a2;
}
请注意,上面的 a1
和 a2
不共享 common_type
,只是兄弟姐妹(共享相同的基数)因此我们不能使用三元运算符。
另请注意,将上述 return 类型更改为 const auto&
并不能解决问题(它不会编译:auto return 类型的推导不一致).
这是一个简单的实现,要求调用者声明预期的 return 类型:
template<typename R>
const R& foo(const auto& a1, const auto& a2) {
if(a1 < a2) return a1;
return a2;
}
然后我们可以调用它:
MyString1 s1 = "hello"; // MyString1 derives from std::string
MyString2 s2 = "world"; // MyString2 also derives from std::string
std::cout << foo<std::string>(s1, s2); // ok we return const lvalue ref
// pointing to one of the above objects
如果不提供预期的 return 值,可能 无法实现的原因有很多。但也许可以以某种方式?
标准库的 std::common_reference<>
非常接近您想要的,并且可以说您的 foo()
函数应该使用什么,因为它清楚地表达了所需的语义:
template<typename T1, typename T2>
std::common_reference_t<const T1&, const T2&> foo(const T1& a1, const T2& a2) {
if(a1 < a2) return a1;
return a2;
}
不幸的是,对于这个特定的 use-case,它不能开箱即用,因为除非其中一种类型派生自另一种类型,否则它无法检测到共同的碱基。
不过,你可以通过特化std::common_type
给它一个提示。像这样:
namespace std {
template<>
struct common_type<MyString1, MyString2> {
using type = std::string;
};
}
它会“正常工作”。您可以在此处查看实际效果:https://gcc.godbolt.org/z/e3PrecPac.
编辑:
值得一提的是,根据您的情况,您还可以为从给定基础派生的所有类型创建 std::common_type
的通用专业化:
struct SomeBase {};
namespace std {
template<std::derived_from<SomeBase> T1, std::derived_from<SomeBase> T2>
struct common_type<T1, T2> {
using type = SomeBase;
};
}
不过,我会对此轻描淡写。它可能是一个非常广泛的 wide-ranging 部分专业化。它很容易导致歧义,特别是如果不止一次。
我可以想到三种方法。
#1 正在等待 for/using 反射。
#2 从后面开始使用 std::tr2::direct_bases
和 std::tr2::bases
。如果您希望能够处理“公共基础不是直接基础,也不是唯一基础”,这将是一件痛苦的事情。
这样做需要一个元编程库,你最终会得到类似这样的东西:
template<class Lhs, class Rhs>
struct common_base< Lhs, Rhs,
std::enable_if_t< always_true< extract_if_unique_t<common_direct_bases_t<Lhs, Rhs>> > >
> {
using type = extract_if_unique_t<common_direct_bases_t<Lhs, Rhs>>;
};
它变得复杂,编写了一堆元编程样板。
#3 提供了您正在寻找的通用碱基的规范列表,并将它们作为您的类型的可能碱基进行搜索。这通常是个好主意,因为这意味着不相关的实现细节类型不会让您出轨(open-closed 原则)。
对于最后一个,我只是对规范碱基列表进行 is_base_of
过滤,以便为两种类型排序,然后获取两个列表中的第一个。
template<template<class...>class Op, class List>
struct filter;
template<class Lhs, class Rhs>
struct intersect_lists;
template<class List>
struct front;
template<class...>
struct types {};
using canonical_bases = types<A,B,C,D>; // in order
template<class Derived>
struct BaseTest {
template<class Base>
using result = std::is_base_of_t< Base, Derived >;
};
template<class Lhs, class Rhs>
using canonical_common_base_of =
front_t< intersect_lists_t<
filter_t<BaseTest<Lhs>::template result, canonical_bases>,
filter_t<BaseTest<Rhs>::template result, canonical_bases>
>>;
用另外几十行元编程(或使用现有的元编程来重现等效的东西)。
我几乎可以肯定,如果没有 反思,我正在寻找的东西就无法完成,这还没有出现在语言中。但偶尔我会对 SO 中的特殊答案感到惊讶,所以让我们试试吧。
是否可以推导出具有共同共享基class的两种类型的“common_base”,所以下面是可能的(伪代码!--语言中没有 common_base_t
,这就是我要实现的魔法):
template<typename T1, typename T2>
const common_base_t<T1, T2>& foo(const T1& a1, const T2& a2) {
if(a1 < a2) return a1;
return a2;
}
请注意,上面的 a1
和 a2
不共享 common_type
,只是兄弟姐妹(共享相同的基数)因此我们不能使用三元运算符。
另请注意,将上述 return 类型更改为 const auto&
并不能解决问题(它不会编译:auto return 类型的推导不一致).
这是一个简单的实现,要求调用者声明预期的 return 类型:
template<typename R>
const R& foo(const auto& a1, const auto& a2) {
if(a1 < a2) return a1;
return a2;
}
然后我们可以调用它:
MyString1 s1 = "hello"; // MyString1 derives from std::string
MyString2 s2 = "world"; // MyString2 also derives from std::string
std::cout << foo<std::string>(s1, s2); // ok we return const lvalue ref
// pointing to one of the above objects
如果不提供预期的 return 值,可能 无法实现的原因有很多。但也许可以以某种方式?
标准库的 std::common_reference<>
非常接近您想要的,并且可以说您的 foo()
函数应该使用什么,因为它清楚地表达了所需的语义:
template<typename T1, typename T2>
std::common_reference_t<const T1&, const T2&> foo(const T1& a1, const T2& a2) {
if(a1 < a2) return a1;
return a2;
}
不幸的是,对于这个特定的 use-case,它不能开箱即用,因为除非其中一种类型派生自另一种类型,否则它无法检测到共同的碱基。
不过,你可以通过特化std::common_type
给它一个提示。像这样:
namespace std {
template<>
struct common_type<MyString1, MyString2> {
using type = std::string;
};
}
它会“正常工作”。您可以在此处查看实际效果:https://gcc.godbolt.org/z/e3PrecPac.
编辑:
值得一提的是,根据您的情况,您还可以为从给定基础派生的所有类型创建 std::common_type
的通用专业化:
struct SomeBase {};
namespace std {
template<std::derived_from<SomeBase> T1, std::derived_from<SomeBase> T2>
struct common_type<T1, T2> {
using type = SomeBase;
};
}
不过,我会对此轻描淡写。它可能是一个非常广泛的 wide-ranging 部分专业化。它很容易导致歧义,特别是如果不止一次。
我可以想到三种方法。
#1 正在等待 for/using 反射。
#2 从后面开始使用 std::tr2::direct_bases
和 std::tr2::bases
。如果您希望能够处理“公共基础不是直接基础,也不是唯一基础”,这将是一件痛苦的事情。
这样做需要一个元编程库,你最终会得到类似这样的东西:
template<class Lhs, class Rhs>
struct common_base< Lhs, Rhs,
std::enable_if_t< always_true< extract_if_unique_t<common_direct_bases_t<Lhs, Rhs>> > >
> {
using type = extract_if_unique_t<common_direct_bases_t<Lhs, Rhs>>;
};
它变得复杂,编写了一堆元编程样板。
#3 提供了您正在寻找的通用碱基的规范列表,并将它们作为您的类型的可能碱基进行搜索。这通常是个好主意,因为这意味着不相关的实现细节类型不会让您出轨(open-closed 原则)。
对于最后一个,我只是对规范碱基列表进行 is_base_of
过滤,以便为两种类型排序,然后获取两个列表中的第一个。
template<template<class...>class Op, class List>
struct filter;
template<class Lhs, class Rhs>
struct intersect_lists;
template<class List>
struct front;
template<class...>
struct types {};
using canonical_bases = types<A,B,C,D>; // in order
template<class Derived>
struct BaseTest {
template<class Base>
using result = std::is_base_of_t< Base, Derived >;
};
template<class Lhs, class Rhs>
using canonical_common_base_of =
front_t< intersect_lists_t<
filter_t<BaseTest<Lhs>::template result, canonical_bases>,
filter_t<BaseTest<Rhs>::template result, canonical_bases>
>>;
用另外几十行元编程(或使用现有的元编程来重现等效的东西)。