编译时间模板实例化检查

tuc*_*cio 10 c++ templates type-traits c++11

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

让我说我有

template <typename T> struct known_type { };
Run Code Online (Sandbox Code Playgroud)

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

小智 16

如果您利用特定表达式可能会或可能不会在需要constexprs的位置使用这一事实,并且您可以查询以查看您拥有的每个候选人的状态,则可以执行此操作.特别是在我们的例子中,constexpr没有定义的s不能作为常量表达式传递,并且noexcept是常量表达式的保证.因此,noexcept(...)返回true信号表示存在正确定义的信号constexpr.

本质上,这将constexprs视为是/否切换,并在编译时引入状态.

请注意,这几乎是一个黑客攻击,您将需要特定编译器的变通方法(请参阅前面的文章),并且这个friend基于特定的实现可能会被未来的标准版本视为格式错误.

随着那个...

用户FilipRoséen专门针对它的文章中提出了这个概念.

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

constexpr int flag (int);
Run Code Online (Sandbox Code Playgroud)

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

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

在我们的程序中,如果你认为旗帜就是这样,它会有所帮助; 句柄(不是函数).原因是我们永远不会在评估的上下文中实际调用flag,我们只对其当前状态感兴趣.

template<class Tag>
struct writer {
  friend constexpr int flag (Tag) {
    return 0;
  }
};
Run Code Online (Sandbox Code Playgroud)

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

如果我们再次将constexpr函数视为某个变量的句柄,我们可以将writer的实例化视为无条件写入可用于friend-declaration中函数后面的变量的值.

template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };
Run Code Online (Sandbox Code Playgroud)

如果你认为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;
}
Run Code Online (Sandbox Code Playgroud)

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

  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`
Run Code Online (Sandbox Code Playgroud)

最后,概念证明:

int main () {
  constexpr int a = f ();
  constexpr int b = f ();
  static_assert (a != b, "fail");
}
Run Code Online (Sandbox Code Playgroud)

我将此应用于您的特定问题.我们的想法是使用constexprYes/No开关来指示是否已实例化类型.因此,您需要为您拥有的每种类型提供单独的开关.

template<typename T>
struct inst_check_wrapper
{
    friend constexpr int inst_flag(inst_check_wrapper<T>);
};
Run Code Online (Sandbox Code Playgroud)

inst_check_wrapper<T>基本上包装一个开关,你可以给它任何类型.它只是原始示例的通用版本.

template<typename T>
struct writer 
{
    friend constexpr int inst_flag(inst_check_wrapper<T>) 
    {
        return 0;
    }
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

template <typename T>
struct MyStruct
{
    template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)>
    MyStruct()
    {}
};
Run Code Online (Sandbox Code Playgroud)

一旦要求特定的构造函数,就会打开该开关.样品:

int main () 
{
    static_assert(!is_instantiated<MyStruct<int>>(), "failure");
    MyStruct<int> a;
    static_assert(is_instantiated<MyStruct<int>>(), "failure");
}
Run Code Online (Sandbox Code Playgroud)

住在Coliru.

  • 我在不同的编译器上检查了您的“概念证明”。它适用于较旧的编译器:C++11 模式下的 gcc 4.7、4.8 和 4.9。但是,它不适用于较新的编译器 gcc 5.2 和 clang 3.6。我检查了所有模式:C++11、C++14 和 C++17。看来默认模板参数不是在实例化时决定的,而是在第一次写出默认模板参数时决定的。这是有道理的,因为其他所有因素都会导致有关单一定义规则(ODR)的奇怪问题。 (4认同)