c++ - 如何在编译时订购类型?

标签 c++ types tuples template-meta-programming c++17

考虑以下程序:

#include <tuple>
#include <vector>
#include <iostream>
#include <type_traits>

template <class T>
struct ordered {};

template <class... T>
struct ordered<std::tuple<T...>>
{
    using type = /* a reordered tuple */;
};

template <class T>
using ordered_t = typename ordered<T>::type;

int main(int argc, char* argv[])
{
    using type1 = std::tuple<char, std::vector<int>, double>;
    using type2 = std::tuple<std::vector<int>, double, char>;
    std::cout << std::is_same_v<type1, type2> << "\n"; // 0
    std::cout << std::is_same_v<ordered_t<type1>, ordered_t<type2>> << "\n"; // 1
    return 0;
}

ordered 帮助器必须对元组中的类型进行重新排序,以便具有相同类型但排序不同的两个元组导致相同的元组类型:可以是第一个,第二个,甚至是另一个:它只需要具有相同的大小和相同的元素,但顺序是唯一的(不管这个顺序如何)。

是否可以在编译时使用模板元编程技术做到这一点?

最佳答案

困难的部分是想出一种订购类型的方法。按谓词对类型列表进行排序是一件苦差事,但也是可行的。我将在这里只关注比较谓词。

一种方法是创建一个类模板,为每种类型定义一个唯一的 id。这很有效,并且可以轻松编写比较器:

template <typename T, typename U>
constexpr bool cmp() { return unique_id_v<T> < unique_id_v<U>; }

但是想出这些唯一的 ID 是一个不一定可行的障碍。您是否将它们全部注册在一个文件中?这不能很好地扩展。

如果我们可以...得到所有类型的名称作为编译时字符串,那就太好了。反射(reflection)会给我们这个,然后这个问题是微不足道的。在那之前,我们可以做一些更脏的事情:使用 __PRETTY_FUNCTION__ . gcc 和 clang 都可以在 constexpr 中使用该宏。上下文,尽管它们对此字符串有不同的格式。如果我们有这样的签名:

template <typename T, typename U>
constexpr bool cmp();

然后 gcc 报告 cmp<char, int>作为 "constexpr bool cmp() [with T = char; U = int]"而 clang 将其报告为 "bool cmp() [T = char, U = int]" .它是不同的......但足够接近我们可以使用相同的算法。基本上是:找出 T 的位置和 U在那里,只是做正常的字符串字典比较:

constexpr size_t cstrlen(const char* p) {
    size_t len = 0;
    while (*p) {
        ++len;
        ++p;
    }
    return len;
}

template <typename T, typename U>
constexpr bool cmp() {
    const char* pf = __PRETTY_FUNCTION__;
    const char* a = pf + 
#ifdef __clang__
        cstrlen("bool cmp() [T = ")
#else
        cstrlen("constexpr bool cmp() [with T = ")
#endif
        ;

    const char* b = a + 1;
#ifdef __clang__
    while (*b != ',') ++b;
#else
    while (*b != ';') ++b;
#endif
    size_t a_len = b - a;
    b += cstrlen("; U = ");
    const char* end = b + 1;
    while (*end != ']') ++end;
    size_t b_len = end - b;    

    for (size_t i = 0; i < std::min(a_len, b_len); ++i) {
        if (a[i] != b[i]) return a[i] < b[i];
    }

    return a_len < b_len;
}

通过一些测试:

static_assert(cmp<char, int>());
static_assert(!cmp<int, char>());
static_assert(!cmp<int, int>());
static_assert(!cmp<char, char>());
static_assert(cmp<int, std::vector<int>>());

这不是最漂亮的实现,我不确定它是否受到标准的有意义的认可,但它可以让您编写您的排序,而无需手动和仔细地注册所有类型。它在 clang 上编译和 gcc .所以也许它已经足够好了。

关于c++ - 如何在编译时订购类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48723974/

相关文章:

c++ - 使用 Magic++ 获取像素

c++ - 使用 boost::future 和 "then"延续

.net - 为什么类在 .Net 中默认不可序列化?

c# - 如何在一行中传递元组结果

python - 如何在 python 中使用带有元组的生成器表达式

python - 将长元组拆分为较小的元组

c++ - 揭秘 lpthreads 下的 gcc

c++如何获取指向另一个类中当前对象的指针?

haskell - 有没有办法在 haskell 中传递未知类型的运算符?

c# - 通用类型参数协变和多接口(interface)实现