仅在使用时如何在成员函数中进行静态断言?

How to static assert in a member function only if it is used?

我有以下方案:

struct Baz {};
struct Qux {};

struct Base {
  virtual ~Base() {}
  virtual void foo() = 0;
};

template<typename T> struct Identity { static bool const value = false; };
template<typename T> void bar(T) { static_assert(Identity<T>::value, "Busted!!!"); }
template<> void bar<Baz>(Baz) {}

template<typename T>
struct Derived : Base {
  T m;
  void foo() { bar(m); }
};

int main() {
  Base *b0 = new Derived<Baz>;
  b0->foo();
  Base *b1 = new Derived<Qux>;
  (void) b1;
}

也就是我有一个纯虚classBase和一个模板classDerived继承自Base并覆盖了纯虚函数foo 根据需要。现在,在 foo 中,我调用函数模板 barbar 有 class Baz 的特化,但没有 class Qux 的特化。在 main 中时,我正在尝试具体化 Derived<Baz> 的对象,一切正常。但是当我尝试实现 Derived<Qux> 的对象时,编译器命中 static_assert.

问:

有没有一种方法可以转换我的代码,使编译器仅在调用 Derived<Qux>::foo() 时才会在 Derived<Qux> 中命中静态断言。

即物化Derived<Qux>的对象会通过:

Base *b1 = new Derived<Qux>;

但是当程序员稍后在代码中尝试调用时:

b1->foo(); // compile error static assert

标准在 [temp.inst]/9 中说了一件有趣的事情:

An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, a static data member of a class template, or a substatement of a constexpr if statement, unless such instantiation is required. It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated.

实例化虚函数的决定取决于实现,但前提是在其他情况下不需要。因此,我们面临的问题是:什么时候需要根据标准本身进行定义?

答案在[class.virtual]/11 and [temp.inst]/2:

A virtual function declared in a class shall be defined, or declared pure in that class, or both; no diagnostic is required

The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class member functions

因此 class 模板的任何实例化都会实例化 Derived::foo 的声明,这通过连锁反应需要一个定义。所以定义 也必须 实例化,如果可用的话。

实现可以行使它在第一个引用段落中给出的回旋余地的唯一方法是 Derived::foo 是否也是纯虚拟的。例如,Clang and GCC 都是这样做的。那当然对你的帮助可能有限。

所以长话短说,只要函数是虚拟的(而不是纯虚拟的),它就不会启动。

@StoryTeller 给出了参考规范等的详细答案,但我将反驳你的问题并询问你真正想做什么。正如问题所写,很明显答案是 "no" 因为你要求编译时错误只能在运行时确定。例如:

Base *b;
if (user_input() == 42) {
     b = new Derived<Baz>();
} else {
     b = new Derived<Qux>();
}
b->foo();

你想要这个案例的编译器错误吗?如果是这样,您需要定义您认为 Qux::foo 应被视为 "called." 的条件 目前,编译器假设在实例化 class 中定义为虚拟的方法被调用.显然您想要不那么保守的东西,但是什么?

如果您有更具体的编译时类型信息,则有可能在运行时捕获错误。例如

Derived<Qux> d = new Derived<Qux>();
d->foo();

如果 foo 是一个非虚模板化方法,它可能会在编译时验证基类型,然后分派给虚方法。 (可能需要更改 foo 的签名才能以某种方式获得类型。)

一个更好的解决方案是将界面中的不同类型的功能分解为不同的 classes 并引入一种机制以在给定的具体 class 上获得特定的界面。如果手头有具体类型class,则可以在编译时检查;如果对接口进行动态查找,则可以在运行时检查。