编译时模板实例化检查

Compile time template instantiation check

本文关键字:实例化 检查 编译      更新时间:2023-10-16

是否可以在编译时检查模板类型是否已实例化,以便我可以在enable_if专用化中使用此信息?

假设我有

template <typename T> struct known_type { };

如果在编译时实例化known_type我可以以某种方式定义一些值为 true 的is_known_type吗?

如果您利用以下事实:特定表达式可能会也可能不会在预期constexpr s的地方使用,并且您可以查询以查看您拥有的每个候选项的状态,则可以执行此操作。具体来说,在我们的例子中,没有定义的constexpr不能作为常量表达式传递,noexcept是常量表达式的保证。因此,noexcept(...)返回true表示存在正确定义的constexpr

从本质上讲,这会将constexpr视为是/否开关,并在编译时引入状态。

请注意,这几乎是一个黑客,您将需要针对特定编译器的解决方法(请参阅前面的文章),并且这种基于friend的特定实现可能会被未来的标准修订版视为格式不正确。

有了这个...

用户菲利普·罗森(Filip Roséen)在他专门针对它的文章中提出了这个概念。

他的示例实现是,并引用了解释:

constexpr int flag (int);

constexpr 函数可以处于两种状态之一;要么是 可以在常量表达式中使用,或者不是 - 如果它缺少 定义它自动属于后一类 - 没有 其他状态(除非我们考虑未定义的行为)。

通常,constexpr 函数应该完全按照它们的样子处理 是;函数,但我们也可以将它们视为单独的句柄 "变量"的类型类似于布尔值,其中每个"变量"可以 具有以下两个值之一;可用或不可用。

在我们的程序中,如果您认为标志就是这样,它会有所帮助; (不是函数)。原因是我们永远不会真正调用标志 在评估的上下文中,我们只对其当前状态感兴趣。

template<class Tag>
struct writer {
  friend constexpr int flag (Tag) {
    return 0;
  }
};

编写器是一个类模板,在实例化后,将创建一个 函数在其周围命名空间中的定义(具有 签名 int 标志 (Tag),其中 Tag 是模板参数)。

如果我们再次将 constexpr 函数视为某些 变量,我们可以将 writer 的实例化视为 无条件写入可用于后面变量的值 在友元声明中的函数。

template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };

如果你认为dependent_writer看起来像一个,我不会感到惊讶 相当毫无意义的间接;为什么不直接实例化作家 我们想在哪里使用它,而不是通过dependent_writer?

  1. 编写器的实例化必须依赖于某些东西来防止立即实例化,并且;
  2. dependent_writer用于可以将 bool 类型的值用作依赖项的上下文中。
template<
  bool B = noexcept (flag (0)),
  int    =   sizeof (dependent_writer<B>)
>
constexpr int f () {
  return B;
}

以上可能看起来有点奇怪,但它真的很简单;

  1. 如果 flag(0) 是常量表达式,则将设置 B = true,否则 B = false,并且;
  2. 隐式实例化dependent_writer(sizeof 需要完全定义的类型)。

该行为可以用以下伪代码表示:

IF [ `int flag (int)` has not yet been defined ]:
  SET `B` =   `false`
  INSTANTIATE `dependent_writer<false>`
  RETURN      `false`
ELSE:
  SET `B` =   `true`
  INSTANTIATE `dependent_writer<true>`
  RETURN      `true`

最后,概念验证:

int main () {
  constexpr int a = f ();
  constexpr int b = f ();
  static_assert (a != b, "fail");
}

我将其应用于您的特定问题。这个想法是使用constexpr是/否开关来指示类型是否已实例化。因此,您需要为每种类型配备一个单独的开关。

template<typename T>
struct inst_check_wrapper
{
    friend constexpr int inst_flag(inst_check_wrapper<T>);
};

inst_check_wrapper<T>本质上是为您可能提供的任何类型的开关包装。它只是原始示例的通用版本。

template<typename T>
struct writer 
{
    friend constexpr int inst_flag(inst_check_wrapper<T>) 
    {
        return 0;
    }
};

开关切换器与原始示例中的切换器相同。它提供了您使用的某种类型的开关的定义。为了便于检查,请添加帮助程序开关检查器:

template <typename T, bool B = noexcept(inst_flag(inst_check_wrapper<T>()))>
constexpr bool is_instantiated()
{
    return B;
}

最后,类型将自身"注册"为初始化。就我而言:

template <typename T>
struct MyStruct
{
    template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)>
    MyStruct()
    {}
};

一旦请求该特定构造函数,开关就会打开。样本:

int main () 
{
    static_assert(!is_instantiated<MyStruct<int>>(), "failure");
    MyStruct<int> a;
    static_assert(is_instantiated<MyStruct<int>>(), "failure");
}

住在科利鲁。

不,无法对未实例化的类进行编译时检查。但是,您可以建立实例化类的(静态)映射(在调试版本中),您可以在运行时检查该映射。

但是,通过将预期的实例化类列表与实际实例化的类进行比较来分析链接的二进制文件应该是可能的(但那是过去的编译时间和我的知识)。

没有办法做到这一点。所以我会说:不。