考虑以下计划:
#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
帮助器必须重新排序元组中的类型,这样两个元组具有相同的类型,但排序方式不同导致相同的元组类型:可以是第一个,第二个,甚至是另一个:它必须具有相同的大小和相同的元素,但是以独特的顺序(不管这个顺序如何)。
是否可以使用模板元编程技术在编译时执行此操作?
答案 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]"
。它有所不同......但足够接近我们可以使用相同的算法。这基本上是:找出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上编译。所以也许它足够好了。
答案 1 :(得分:1)
我认为以前的答案有点特质-至少在实现上是这样的。
这时,我们有一个非常不错的,多编译器支持的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
// 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。