Can Virtual Functions Be Constexpr

Can virtual functions be constexpr?

This answer is no longer correct as of C++20.

No. From [dcl.constexpr]/3 (7.1.5, "The constexpr specifier"):

The definition of a constexpr function shall satisfy the following requirements:

— it shall not be virtual

What is the advantage of constexpr virtual functions in C++20?

Well the obvious benefit is that you can even do virtual function calls at compile time now.

struct Base {
constexpr virtual int get() { return 1; }
virtual ~Base() = default;
};

struct Child : Base {
constexpr int get() override { return 2; }
};

constexpr int foo(bool b) {
Base* ptr = b ? new Base() : new Child();
auto res = ptr->get(); // this call is not possible prior to C++20
delete ptr;

return res;
}

constexpr auto BaseVal = foo(true);
constexpr auto ChildVal = foo(false);

You can't use the get function via a base pointer in a constant expression prior to C++20. If you make it constexpr, you can though. Example.


Now thinking about what benefit we could get from virtual function calls at compile time: maybe compile times. C++ has basically two mechanisms to deal with polymorphism:

  • templates, and
  • virtual functions.

Both solve essentially the same problems but at different stages in your program's life time. Of course it's nice to do as much computation as possible at compile time and therefore have the best performance at run time. However, this is not always a feasible approach because compile time can explode quickly due to how templates work.

Speculations start here. Now what if we broaden the stages at which virtual functions can be called and also allow them to be called at compile time? This would allow us, in some cases, to replace heavily recursive or nested templates with virtual function calls. Assuming that the constexpr interpreter is faster than the compiler recursively resolving templates, you could see some compile time reductions.

Of course this benefit is overshadowed by the performance increases you'll get from concepts and modules.


Another benefit lies in the nature of constexpr in general: UB is forbidden during constant evaluation. This means you could check if your virtual functions are UB free with a few static asserts.

How are virtual constexpr function possible?

Please keep in mind that constexpr virtual functions would be called at compile time only when the type is already known to the compiler and obviously they would not be called through virtual dispatch.

Corresponding proposal provides similar explanation:

Virtual function calls are currently prohibited in constant
expressions. Since in a constant expression the dynamic type of the
object is required to be known (in order to, for example, diagnose
undefined behavior in casts), the restriction is unnecessary and
artificial. We propose the restriction be removed.

It also has a very nice motivating example.

C++ constexpr final virtual functions

  • C++20 adds support for this code.
  • On godbolt, we can try it out: https://godbolt.org/z/f64e93dzY
  • It looks like this is Defect Report 647

Does this constexpr virtual function technique violate any C++11/C++14 rule?

The rule in [dcl.constexpr] is pretty clear:

The definition of a constexpr function shall satisfy the following requirements:

— it shall not be virtual (10.3);

literal_a::method and literal_b::method are both virtual because each they override literal_base::method, which is virtual. Hence, they cannot be constexpr. It does not matter that they are final. The program is ill-formed.

It is true that a literal type is allowed to have a virtual member function though.

constexpr and virtual

This restriction exists since constexpr was introduced in C++11:

10.1.5 The constexpr specifier [dcl.constexpr]


3 The definition of a constexpr function shall satisfy the following requirements:

(3.1) - it shall not be virtual;


But you're asking about rationale for this restriction, and not about the restriction itself.

The fact is, it may just be an oversight. Since in a constant expression the dynamic type of the object is required to be known, this restriction is unnecessary and artificial: This is what Peter Dimov and Vassil Vassilev assert in P1064R0, where they propose to remove it.

In fact, the wording for it no longer exists in the current draft.

Implicitly virtual constexpr function

The compilers have a bug. Note that this has been fixed in clang 3.5 already, not sure why you don't get an error, because I do.

The standard is pretty explicit about this in [dcl.constexpr]p3:

The definition of a constexpr function shall satisfy the following requirements:

  • it shall not be virtual;
  • [...]

It does't matter whether doSomething is implicitly virtual or not. In both cases, it is considered to be virtual, and so it violates the point above.

Can virtual functions be C++20 coroutines?

Yes.

From cppreference

Restrictions

Coroutines cannot use variadic arguments, plain return statements, or placeholder return types (auto or Concept).

Constexpr functions, constructors, destructors, and the main function cannot be coroutines.

Knowing how they work, I don't see any reason why it wouldn't be allowed.

constexpr result from non-constexpr call

As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.

There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.

The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).

Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.

Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.

Note that, as mentioned above, the rules are stricter if you use references, e.g.

A a;
A& ar = a;
constexpr int kInt = f(ar);

will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)



Related Topics



Leave a reply



Submit