如何在编译时订购类型

How to order types at compile-time?

本文关键字:类型 编译      更新时间:2023-10-16

考虑以下程序:

#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是一个不一定可行的障碍。您是否将它们全部注册到一个文件中?那不是超级扩展。

如果我们可以...获取所有类型的 name 作为编译时间字符串的 name 。反思会给我们这一点,然后这个问题是微不足道的。在此之前,我们可以做一些更脏的事情:使用__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]"。这是不同的...但是足够近,我们可以使用相同的算法。这基本上是:找出TU在哪里,只是进行正常的字符串词典比较:

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上编译。所以也许足够好。

这是对Barry提出的方法的轻微修改,该方法与Visual Studio一起使用。而不是创建存储函数名称的编译时字符串:

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

此方法直接比较了Type_name&lt&lt;t> :: name((。当Macro __pretty_function__返回的类型T和U的名称被逗号分隔时,Barry的方法不起作用,因为逗号也可以分开模板参数,当t或u是类或功能模板时。

// length of null-terminated string
constexpr size_t cstrlen(const char* p) 
{
    size_t len = 0;
    while (*p) 
    {
        ++len;
        ++p;
    }
    return len;
}
// constexpr string representing type name
template<class T>
struct type_name
{
    static constexpr const char* name() 
    { 
        #if defined (_MSC_VER)
            return __FUNCSIG__; 
        #else
            return __PRETTY_FUNCTION__;
        #endif
    };
};
// comparison of types based on type names
template<class T1, class T2>
constexpr bool less()
{
    const char* A   = type_name<T1>::name();
    const char* B   = type_name<T2>::name();
    size_t a_len    = cstrlen(A);
    size_t b_len    = cstrlen(B);
    size_t ab_len   = (a_len < b_len) ? a_len : b_len;
    for (size_t i = 0; i < ab_len; ++i) 
    {
        if (A[i] != B[i]) 
            return A[i] < B[i];
    }
    return a_len < b_len;
}
// simple checks
template<class ... Type>
struct list;
static_assert(less<list<int, void, list<void, int>>, list<int, void, list<void, void>>>());
static_assert(less<list<int, void, list<void, int>>, list<int, void, list<void, int>, int>>());

此方法在VS上起作用。我不确定它在clang还是GCC上有效。

tl; dr:在编译时获取类型名称,然后订购。

我认为,以前的答案有点特殊 - 至少在实施中。

在这一点上,我们有一个非常不错的,多编译器支持的功能,可以作为字符串视图获取类型的名称作为编译时字符串。我在这里只引用其签名:

template <typename T>
constexpr std::string_view type_name();

这构成了从类型到编译时可弥补的值的注入映射。鉴于这些,您可以轻松地实现一个类似选择的过程,以获取每种类型的相对顺序。最后,您使用这些订单组装新的元组。