如何在编译时枚举,排序等类?

时间:2015-12-29 23:40:04

标签: c++ template-meta-programming compile-time

我正在努力解决可以推入编译时计算的一些规则。在这里,我编写了一个代码,将唯一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将取决于排序;然后要确定“最后”元素,你必须手动排序元素!我错过了什么?

2 个答案:

答案 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成功为止。博客文章相当好地解释了正在发生的事情。

限制

(从文章中解除)

  • 您不能在翻译单位中使用相同的计数器,否则您可能会违反ODR。
  • 注意constexpr生成的值之间的一些比较运算符;尽管您的调用顺序,但有时无法保证编译器实例化它们的相对时间。 (我们可以用std::atomic对此做些什么吗?)
    • 这意味着如果在编译时进行评估,a < b不能保证为真,即使它是在运行时进行的。
  • 模板参数替换顺序;可能导致C ++ 11编译器的行为不一致;用C ++ 14
  • 修复
  • MSVC支持:即使Visual Studio 2015附带的编译器仍然不完全支持表达式SFINAE。博客文章中提供了解决方法。

将计数器转换为与类型相关联的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]:

  

可以在。中声明文字类型的静态数据成员   使用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");

注意int1递增时char如何保持2 反对friend constexpr int adl_flag(flag<N>)

Live Demo

此代码具有与以前相同的所有缺点 (可能还有更多我没有)

注意

由于每个整数值的{{1}}声明很多,因此使用此代码会有大量的编译器警告;事实上,每个未使用的计数器值都有一个。