如何在编译时订购类型?

时间:2018-02-10 18:09:01

标签: 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帮助器必须重新排序元组中的类型,这样两个元组具有相同的类型,但排序方式不同导致相同的元组类型:可以是第一个,第二个,甚至是另一个:它必须具有相同的大小和相同的元素,但是以独特的顺序(不管这个顺序如何)。

是否可以使用模板元编程技术在编译时执行此操作?

3 个答案:

答案 0 :(得分:18)

困难的部分是想出一种订购类型的方法。通过谓词对类型列表进行排序是一件苦差事,但是可行。我将重点关注比较谓词。

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

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

但是提出这些独特的ID是一个不一定可行的障碍。你在一个文件中注册它们吗?这不能很好地扩展。

如果我们能够......将所有类型的名称作为编译时间字符串,那将是多么美妙的事情。反思会给我们这个,然后这个问题是微不足道的。在那之前,我们可以做一些更脏的事情:使用__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>>());

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

答案 1 :(得分:1)

tl; dr:在编译时获取类型名称,并以此排序。

我认为以前的答案有点特质-至少在实现上是这样的。

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

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

这构成了从类型到可与编译时比较的值的内射映射。有了这些,您就可以轻松实现类似于selection-sort的过程来获取每种类型的相对顺序。最后,使用这些命令组装一个新的元组。

答案 2 :(得分:0)

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

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

此方法直接比较type_name :: name()返回的两种类型的名称。当宏__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 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。