Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have the following scheme:

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

That is, I have a pure virtual class Base and a template class Derived that inherits from Base and overrides the pure virtual function foo as required. Now, inside foo I call function template bar. bar has a specialization for class Baz but not for class Qux. When in main I'm trying to materialize an object of Derived<Baz> everything's OK. But when I try to materialize an object of Derived<Qux> compiler hits static_assert.

Q:

Is there a way to transform my code in such a way that compiler will hit static assert in Derived<Qux> only if Derived<Qux>::foo() is called.

That is, materializing an object of Derived<Qux> will pass:

Base *b1 = new Derived<Qux>;

But when later in code the programmer tries to call:

b1->foo(); // compile error static assert
like image 658
101010 Avatar asked Sep 21 '17 10:09

101010


2 Answers

The standard says an interesting thing at [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.

The decision of instantiating a virtual function is up to the implementation, but only if it is not needed otherwise. The question we are faced with is therefore: when is the definition needed according to the standard itself?

The answer is at [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

So any instantiation of the class template, will instantiate a declaration of Derived::foo, which by a chain reaction requires a definition. So the definition must be instantiated too, if it is available.

The only way an implementation can exercise the leeway it is given in the first quoted paragraph, is if Derived::foo is pure virtual too. As an example, both Clang and GCC do just that. That of course is likely to be of limited help to you.

So to make a long story short, it's a no-starter, so long as the function is virtual (and not pure virtual).

like image 139
StoryTeller - Unslander Monica Avatar answered Sep 30 '22 12:09

StoryTeller - Unslander Monica


@StoryTeller gives a detailed answer referencing the spec, etc. but I'm going to push back on your question and ask what you are really trying to do. As the question is written, it is dead obvious the answer is "no" because you are asking for a compile time error on something that is only determinable at runtime. E.g.:

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

Do you want a compiler error for this case? If so you'll need to define the conditions under which you think Qux::foo should be considered to be "called." At present the compiler is assuming a method defined as virtual in an instantiated class is called. Clearly you want something less conservative, but what?

If you have more specific compile time type information, it may be possible to catch the error at runtime. E.g.

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

If foo is a non-virtual templated method, it is possible it could validate the base type at compile time and then dispatch to the virtual method. (It will likely require changing the signature of foo to have the type somehow.)

A far better solution would be to break different types of functionality in the interface into different classes and introduce a mechanism to get specific interfaces on a given concrete class. This can be checked at compile time if you have the concretely types class in hand and at runtime if doing a dynamic lookup of an interface.

like image 25
Zalman Stern Avatar answered Sep 30 '22 11:09

Zalman Stern