在 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;
}

请注意,上面的 a1a2 不共享 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_basesstd::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>
  >>;

用另外几十行元编程(或使用现有的元编程来重现等效的东西)。