类如何在编译时进行枚举、排序等?
How can classes be enumerated, ordered, etc. at compile time?
我正在努力解决一些可以推入编译时计算的规则。在这里,我编写了代码,将唯一 ID 与请求一个的每个 class 相关联(以及用于测试目的的 demangled 名称。)但是,此唯一 ID 不能用作模板参数或模板参数的一部分static_assert 条件,因为它不是一个 constexpr。
#include <cassert>
#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
namespace UID {
static int nextID(void) {
static int stored = 0;
return stored++;
}
template<class C>
static int getID(void) {
static int once = nextID();
return once;
}
template<class C>
static const char *getName(void) {
static int status = -4;
static const char *output =
abi::__cxa_demangle(typeid(C).name(), 0, 0, &status);
return output;
}
}
namespace Print {
template<class C>
std::ostream& all(std::ostream& out) {
return out << "[" << UID::getID<C>() << "] = "
<< UID::getName<C>() << std::endl;
}
template<class C0, class C1, class... C_N>
std::ostream& all(std::ostream& out) {
return all<C1, C_N>(all<C0>(out));
}
}
void test(void) {
Print::all<int, char, const char*>(std::cout) << std::endl;
// [0] = int
// [1] = char
// [2] = char const*
Print::all<char, int, const char*>(std::cout);
// [1] = char
// [0] = int
// [2] = char const*
}
如果不清楚,我想根据 ID 更改其他编译时行为。我见过几种涉及类型链接列表的方法,因此 ID 是先前分配的 constexpr ID 和 constexpr 偏移量的总和。但是,我看不出这比手动分配 ID 有何改进。如果您要按 ID 对 classes 的列表进行排序,然后包装每个 classes 并为包装器请求 ID,则 ID 将取决于排序;那么要确定 "last" 元素,您将不得不手动对元素进行排序!我错过了什么?
有时,不得不承认 C++ 本身并不能解决世界上所有的问题。
有时,有必要将额外的工具和脚本集成到一个人的构建系统中。我认为这是其中一种情况。
但首先,让我们只使用 C++ 来尽可能多地解决这个问题。我们将使用 Curiously Recursive Template Pattern:
template<typename C> class UID {
public:
static const int id;
};
然后,每个请求唯一 ID 的 class 将从该模板继承,因此,会产生一个名为 id
:
的成员
class Widget : public UID<Widget> {
// ...
};
因此,Widget::id
成为 class 的唯一 ID。
现在,我们需要做的就是找出如何声明所有 classes 的 id
值。而且,在这一点上,我们已经达到了 C++ 自身所能做的极限,我们必须调用一些增援。
我们将首先创建一个文件,其中列出所有 class 具有指定 ID 的元素。这并不复杂,只是一个简单的文件,例如,名为 classlist
,其内容就是这样。
Button
Field
Widget
(按钮、字段和小部件不是从 UID class 继承的 class)。
现在,它变成了一个简单的两步过程:
1) 一个简单的 shell 或 Perl 脚本,它读取 classlist
文件,并输出 robo-generated 形式的代码(给定上述输入):
const int UID<Button>::id=0;
const int UID<Field>::id=1;
const int UID<Widget>::id=2;
...等等。
2) 对您的构建脚本或 Makefile
进行适当的调整,以编译此 robo-generated 代码(使用所有必要的 #include
,等等...,以实现此目的),并 link 它与您的应用程序。因此,想要为其分配 ID 的 class 必须显式继承 UID
class,并将其名称添加到文件中。然后构建 script/Makefile 会自动运行生成新 uid 列表的脚本,并在下一个构建周期编译它。
(希望您是使用真正的C++开发环境,它为您提供了灵活的开发工具,而不是被迫遭受一些不灵活的visual-IDE类型限制开发环境,功能有限)。
这只是一个起点。再多做一点工作,应该可以采用这种基本方法,并将其增强为 auto-generate constexpr
uid,这样会更好。这将需要破解一些棘手的问题,例如当 UID-using classes 列表发生变化时,试图避免触发整个应用程序的重新编译。但是,我认为这也是一个可以解决的问题...
后记:
通过利用 compiler-specific 扩展,仍然可以仅使用 C++ 来完成此操作。例如,使用 gcc's __COUNTER__ macro.
这是一个非常有趣的问题,因为它不仅与在 C++ 中实现 compile-time 的计数器有关,还与将(静态)计数器值与 compile-time.[=25 的类型相关联有关=]
所以我研究了一下,发现了一个非常有趣的博客 post
How to implement a constant expression counter in C++ by Filip Roséen
他实现的计数器确实扩展了 ADL 和 SFINAE 的工作范围:
template<int N>
struct flag {
friend constexpr int adl_flag (flag<N>);
};
template<int N>
struct writer {
friend constexpr int adl_flag (flag<N>) {
return N;
}
static constexpr int value = N;
};
template<int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>) {
return N;
}
template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {})) {
return R;
}
int constexpr reader (float, flag<0>) {
return 0;
}
template<int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value) {
return R;
}
int main () {
constexpr int a = next ();
constexpr int b = next ();
constexpr int c = next ();
static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}
本质上它依赖于 ADL 未能找到 friend
函数的适当定义,导致 SFINAE,并使用模板递归直到完全匹配或 ADL 成功。博客 post 很好地解释了正在发生的事情。
限制
(摘自文章)
- 您不能在多个翻译单元中使用相同的计数器,否则您可能会违反 ODR。
- 注意 constexpr 生成的值之间的一些比较运算符;尽管调用顺序不同,但有时无法保证编译器实例化它们的相对时间。 (我们可以用
std::atomic
做些什么吗?)
- 这意味着如果在 compile-time 时评估
a < b
不能保证为真,即使它会在 运行 之前。
- 模板参数替换顺序;可能导致跨 C++11 编译器的行为不一致;固定在 C++14
- MSVC 支持:即使 Visual Studio 2015 附带的编译器仍然不完全支持表达式 SFINAE。博客 post.
中提供的解决方法
将计数器变成 type-associated UUID
事实证明,更改起来非常简单:
template<int N = 1, int C = reader (0, flag<32> ())>
int constexpr next (int R = writer<C + N>::value) {
return R;
}
进入
template<typename T, int N = 1>
struct Generator{
static constexpr int next = writer<reader (0, flag<32> {}) + N>::value; // 32 implies maximum UUID of 32
};
鉴于 const static int
是您可以声明和定义的少数类型之一
在同一个地方 [9.4.2.3]:
A static data member of literal type can be declared in the
class definition with the constexpr specifier; if so, its declaration shall
specify a brace-or-equal-initializer
in which every initializer-clause that is an assignment-expression is a constant
expression. [ Note: In both
these cases, the member may appear in constant expressions. — end note ]
所以现在我们可以这样写代码了:
constexpr int a = Generator<int>::next;
constexpr int b = Generator<int>::next;
constexpr int c = Generator<char>::next;
static_assert(a == 1, "try again");
static_assert(b == 1, "try again");
static_assert(c == 2, "try again");
注意 int
如何保持 1
而 char
递增
反对 2
.
此代码存在与以前相同的所有缺点
(可能还有更多我没有想到的)
备注
由于每个整数值的 friend constexpr int adl_flag(flag<N>)
声明如此之多,因此此代码会出现大量编译器警告;实际上每个未使用的计数器值一个。
我正在努力解决一些可以推入编译时计算的规则。在这里,我编写了代码,将唯一 ID 与请求一个的每个 class 相关联(以及用于测试目的的 demangled 名称。)但是,此唯一 ID 不能用作模板参数或模板参数的一部分static_assert 条件,因为它不是一个 constexpr。
#include <cassert>
#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
namespace UID {
static int nextID(void) {
static int stored = 0;
return stored++;
}
template<class C>
static int getID(void) {
static int once = nextID();
return once;
}
template<class C>
static const char *getName(void) {
static int status = -4;
static const char *output =
abi::__cxa_demangle(typeid(C).name(), 0, 0, &status);
return output;
}
}
namespace Print {
template<class C>
std::ostream& all(std::ostream& out) {
return out << "[" << UID::getID<C>() << "] = "
<< UID::getName<C>() << std::endl;
}
template<class C0, class C1, class... C_N>
std::ostream& all(std::ostream& out) {
return all<C1, C_N>(all<C0>(out));
}
}
void test(void) {
Print::all<int, char, const char*>(std::cout) << std::endl;
// [0] = int
// [1] = char
// [2] = char const*
Print::all<char, int, const char*>(std::cout);
// [1] = char
// [0] = int
// [2] = char const*
}
如果不清楚,我想根据 ID 更改其他编译时行为。我见过几种涉及类型链接列表的方法,因此 ID 是先前分配的 constexpr ID 和 constexpr 偏移量的总和。但是,我看不出这比手动分配 ID 有何改进。如果您要按 ID 对 classes 的列表进行排序,然后包装每个 classes 并为包装器请求 ID,则 ID 将取决于排序;那么要确定 "last" 元素,您将不得不手动对元素进行排序!我错过了什么?
有时,不得不承认 C++ 本身并不能解决世界上所有的问题。
有时,有必要将额外的工具和脚本集成到一个人的构建系统中。我认为这是其中一种情况。
但首先,让我们只使用 C++ 来尽可能多地解决这个问题。我们将使用 Curiously Recursive Template Pattern:
template<typename C> class UID {
public:
static const int id;
};
然后,每个请求唯一 ID 的 class 将从该模板继承,因此,会产生一个名为 id
:
class Widget : public UID<Widget> {
// ...
};
因此,Widget::id
成为 class 的唯一 ID。
现在,我们需要做的就是找出如何声明所有 classes 的 id
值。而且,在这一点上,我们已经达到了 C++ 自身所能做的极限,我们必须调用一些增援。
我们将首先创建一个文件,其中列出所有 class 具有指定 ID 的元素。这并不复杂,只是一个简单的文件,例如,名为 classlist
,其内容就是这样。
Button
Field
Widget
(按钮、字段和小部件不是从 UID class 继承的 class)。
现在,它变成了一个简单的两步过程:
1) 一个简单的 shell 或 Perl 脚本,它读取 classlist
文件,并输出 robo-generated 形式的代码(给定上述输入):
const int UID<Button>::id=0;
const int UID<Field>::id=1;
const int UID<Widget>::id=2;
...等等。
2) 对您的构建脚本或 Makefile
进行适当的调整,以编译此 robo-generated 代码(使用所有必要的 #include
,等等...,以实现此目的),并 link 它与您的应用程序。因此,想要为其分配 ID 的 class 必须显式继承 UID
class,并将其名称添加到文件中。然后构建 script/Makefile 会自动运行生成新 uid 列表的脚本,并在下一个构建周期编译它。
(希望您是使用真正的C++开发环境,它为您提供了灵活的开发工具,而不是被迫遭受一些不灵活的visual-IDE类型限制开发环境,功能有限)。
这只是一个起点。再多做一点工作,应该可以采用这种基本方法,并将其增强为 auto-generate constexpr
uid,这样会更好。这将需要破解一些棘手的问题,例如当 UID-using classes 列表发生变化时,试图避免触发整个应用程序的重新编译。但是,我认为这也是一个可以解决的问题...
后记:
通过利用 compiler-specific 扩展,仍然可以仅使用 C++ 来完成此操作。例如,使用 gcc's __COUNTER__ macro.
这是一个非常有趣的问题,因为它不仅与在 C++ 中实现 compile-time 的计数器有关,还与将(静态)计数器值与 compile-time.[=25 的类型相关联有关=]
所以我研究了一下,发现了一个非常有趣的博客 post How to implement a constant expression counter in C++ by Filip Roséen
他实现的计数器确实扩展了 ADL 和 SFINAE 的工作范围:
template<int N>
struct flag {
friend constexpr int adl_flag (flag<N>);
};
template<int N>
struct writer {
friend constexpr int adl_flag (flag<N>) {
return N;
}
static constexpr int value = N;
};
template<int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>) {
return N;
}
template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {})) {
return R;
}
int constexpr reader (float, flag<0>) {
return 0;
}
template<int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value) {
return R;
}
int main () {
constexpr int a = next ();
constexpr int b = next ();
constexpr int c = next ();
static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}
本质上它依赖于 ADL 未能找到 friend
函数的适当定义,导致 SFINAE,并使用模板递归直到完全匹配或 ADL 成功。博客 post 很好地解释了正在发生的事情。
限制
(摘自文章)
- 您不能在多个翻译单元中使用相同的计数器,否则您可能会违反 ODR。
- 注意 constexpr 生成的值之间的一些比较运算符;尽管调用顺序不同,但有时无法保证编译器实例化它们的相对时间。 (我们可以用
std::atomic
做些什么吗?)- 这意味着如果在 compile-time 时评估
a < b
不能保证为真,即使它会在 运行 之前。
- 这意味着如果在 compile-time 时评估
- 模板参数替换顺序;可能导致跨 C++11 编译器的行为不一致;固定在 C++14
- MSVC 支持:即使 Visual Studio 2015 附带的编译器仍然不完全支持表达式 SFINAE。博客 post. 中提供的解决方法
将计数器变成 type-associated UUID
事实证明,更改起来非常简单:
template<int N = 1, int C = reader (0, flag<32> ())>
int constexpr next (int R = writer<C + N>::value) {
return R;
}
进入
template<typename T, int N = 1>
struct Generator{
static constexpr int next = writer<reader (0, flag<32> {}) + N>::value; // 32 implies maximum UUID of 32
};
鉴于 const static int
是您可以声明和定义的少数类型之一
在同一个地方 [9.4.2.3]:
A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. — end note ]
所以现在我们可以这样写代码了:
constexpr int a = Generator<int>::next;
constexpr int b = Generator<int>::next;
constexpr int c = Generator<char>::next;
static_assert(a == 1, "try again");
static_assert(b == 1, "try again");
static_assert(c == 2, "try again");
注意 int
如何保持 1
而 char
递增
反对 2
.
此代码存在与以前相同的所有缺点 (可能还有更多我没有想到的)
备注
由于每个整数值的 friend constexpr int adl_flag(flag<N>)
声明如此之多,因此此代码会出现大量编译器警告;实际上每个未使用的计数器值一个。