我正在努力解决可以推入编译时计算的一些规则。在这里,我编写了一个代码,将唯一ID与请求一个的每个类相关联(以及用于测试目的的解码名称。)但是,此唯一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对一个类列表进行排序,那么包装每个类并为包装器请求ID,ID将取决于排序;然后要确定“最后”元素,你必须手动排序元素!我错过了什么?
答案 0 :(得分:1)
有时,人们必须承认C ++本身并不能解决世界上所有的问题。
有时,有必要将其他工具和脚本集成到一个构建系统中。我认为这是其中一种情况。
但首先,让我们使用C ++来解决尽可能多的问题。我们将使用Curiously Recursive Template Pattern:
template<typename C> class UID {
public:
static const int id;
};
然后,每个请求唯一ID的类将继承此模板,从而产生一个名为id
的成员:
class Widget : public UID<Widget> {
// ...
};
因此,Widget::id
成为班级的唯一ID。
现在,我们需要做的就是弄清楚如何声明所有类的id
值。而且,在这一点上,我们达到了C ++本身可以做的极限,我们必须加入一些增援。
我们首先创建一个列出所有具有指定ID的类的文件。这并不复杂,只是一个名为classlist
的简单文件,其内容就是这样的。
Button
Field
Widget
(Button,Field和Widget,是从UID类继承的其他类)。
现在,它变成了一个简单的两步过程:
1)一个简单的shell或Perl脚本,它读取classlist
文件,并喷出robo生成的表单代码(给定上面的输入):
const int UID<Button>::id=0;
const int UID<Field>::id=1;
const int UID<Widget>::id=2;
......等等。
2)对构建脚本或Makefile
进行适当的调整,以编译这个机器人生成的代码(包含所有必需的#include
等等,以实现此目的),并链接它与您的应用程序。因此,需要为其分配ID的类必须显式继承UID
类,并将其名称添加到文件中。然后,构建脚本/ Makefile会自动运行一个脚本,该脚本生成一个新的uid列表,并在下一个构建周期中对其进行编译。
(希望您 使用真正的C ++开发环境,它为您提供灵活的开发工具,而不是被迫遭受一些不灵活的visual-IDE类型有限的开发环境,功能有限)
这只是一个起点。通过更多的工作,应该可以采用这种基本方法,并将其增强为自动生成constexpr
uid,这将更好。这将需要破解一些棘手的问题,例如当UID使用类的列表发生变化时,试图避免触发重新编译整个应用程序。但是,我认为这也是一个可以解决的问题......
Postscriptum:
通过利用特定于编译器的扩展,仍然可以仅使用C ++来实现此目的。例如,使用gcc's __COUNTER__ macro。
答案 1 :(得分:1)
这是一个非常有趣的问题,因为它不仅与在C ++中编译时实现计数器有关,而且还与在编译时将(静态)计数器值与类型相关联。
所以我研究了一下,发现了一篇非常有趣的博文 How to implement a constant expression counter in C++的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成功为止。博客文章相当好地解释了正在发生的事情。
(从文章中解除)
std::atomic
对此做些什么吗?)
a < b
不能保证为真,即使它是在运行时进行的。事实证明,改变真的很简单:
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]:
可以在。中声明文字类型的静态数据成员 使用constexpr说明符的类定义;如果是的话,其声明应 指定一个大括号或等于初始化程序 其中作为赋值表达式的每个initializer子句都是常量 表达。 [注意:两者都有 在这些情况下,成员可能出现在常量表达式中。 - 结束说明]
现在我们可以编写如下代码:
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>)
。
此代码具有与以前相同的所有缺点 (可能还有更多我没有)
由于每个整数值的{{1}}声明很多,因此使用此代码会有大量的编译器警告;事实上,每个未使用的计数器值都有一个。