编译时模板实例化检查

13

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

假设我有以下代码:

template <typename T> struct known_type { };

如果在编译时实例化了一种已知的类型,我是否可以定义某个 is_known_type(其值为 true)?


请问您能否举个例子,展示如何在 enable_if 中使用这种信息? - erenon
1
接受的答案似乎在新编译器上无法工作。看起来这个答案依赖于一些GCC编译器的错误,而且这个解决方案在符合标准的编译器上不起作用。 - Ralph Tandetzky
3个回答

20
如果你利用特定表达式可能在期望constexpr的地方使用或不使用的事实,并且你可以查询每个候选项的状态,那么就有可能做到这一点。具体来说,在我们的情况下,没有定义的constexpr不能作为常量表达式传递,而noexcept是一个常量表达式的保证。因此,noexcept(...)返回true表示存在正确定义的constexpr

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

请注意,这几乎是一种hack,你需要针对特定编译器找到解决方法(请参阅后面的文章),而这个特定的基于friend的实现可能被未来的标准修订认为是非法的。

说了这些...

用户Filip Roséen他的文章中专门介绍了这个概念。

他的示例实现如下,带有引用的解释:
constexpr int flag (int);

一个constexpr函数可以处于两种状态之一;要么它可用于常量表达式,要么不行 - 如果它缺少定义,则自动落入后一类别 - 没有其他状态(除非我们考虑未定义行为)。
通常,constexpr函数应该被视为函数,但我们也可以将它们视为对“变量”的单独句柄,其类型类似于bool,其中每个“变量”可以具有两个值之一;可用或不可用。
在我们的程序中,如果您将flag视为仅仅是一个句柄(而不是一个函数),那么会很有帮助。原因是我们实际上永远不会在评估上下文中调用flag,我们只关心它的当前状态。
template<class Tag>
struct writer {
  friend constexpr int flag (Tag) {
    return 0;
  }
};

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

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

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

如果你认为dependent_writer看起来像一个毫无意义的间接引用,我不会感到惊讶;为什么不直接在需要使用writer的地方实例化它,而是通过dependent_writer呢?

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

The above might look a little weird, but it's really quite simple;

  1. will set B = true if flag(0) is a constant-expression, otherwise B = false, and;
  2. implicitly instantiates dependent_writer (sizeof requires a completely-defined type).

The behavior can be expressed with the following pseudo-code:

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的Yes/No开关来指示一个类型是否被实例化。因此,您需要为每种类型都设置一个单独的开关。
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;
}

最后,类型“registers”本身被标记为已初始化。在我的情况下:

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");
}

在Coliru上直播。


3
我在不同的编译器上检查了你的“概念验证”。它适用于旧版本的编译器:gcc 4.7、4.8和4.9,使用C++11模式。然而,它不适用于更新的编译器gcc 5.2和clang 3.6。我检查了所有模式:C++11、C++14和C++17。看起来默认模板参数在实例化时并没有被决定,而是在第一次写出默认模板参数时被确定。这是有道理的,因为其他情况都会导致关于单一定义规则(ODR)的奇怪问题。 - Ralph Tandetzky
@RalphTandetzky 如果所有涉及的函数都是constexpr,这意味着inline,那么为什么该实现会存在odr问题? - ABu

1

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

然而,通过比较预期实例化类列表与实际实例化类来分析链接的二进制文件应该是可能的(但这已经超出了编译时和我的知识范围)。


我的问题是解决结构体重叠部分特化的问题,我有一个特化应该只适用于已知类型,但这些类型直到编译时才真正知道,因为我正在编写一个模板库。 - tuccio

-1

没有办法做到那样。所以我会说:不行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接