std::declval and decltype
About decltype
decltype(expr)
is a new keyword added to C++11 to type out entities or expressions.
It is simple and needs no additional explanation.
But how can something so simple require such a big thing as a new keyword? It’s metaprogramming! In the world of metaprogramming, a long string of template class declarations is crippling, and writing them repeatedly is even more tedious. For example, a runtime debug log output.
It’s not the longest name I can remember, just the one reference that I can intercept most easily. There are plenty of examples like this.
Here’s a slightly rewritten example to illustrate the usefulness of decltype.
Obviously, using M = decltype(m)
is more concise, especially when machine_t<my_state, void, payload_t<my_state>>
may be a super-long string with a super-long definition of template arguments, the value of decltype becomes more obvious.
In metaprogramming, especially in cases where large class systems are tangled with each other, there are many times when the power of decltype and auto-derivation may not be available, because we may not be able to predict what the specific type will be in a specific scenario.
Standardized coding style
In addition, making good use of decltype and using can contribute to the standardization and effortlessness of your code.
When writing a class, we should make more use of the type aliasing capabilities provided by using, which of course may also involve the use of decltype.
The advantage of using is that the compiler can be explicitly prompted to do the relevant type derivation in advance, and if there is a mistake, it can be fixed at a set of using statements, rather than having to go through a bunch of code paragraphs to figure out why the wrong type was used.
Using the wrong type can lead to a huge pile of code that has to be rewritten.
Using using can also help you reduce the number of code paragraphs you have to change. For example, if using Container=std::list<T>
is changed to using Container=std::vector<T>
, your already written code paragraphs and even the Container _container
declaration can be changed without a single change, just by recompiling.
This section does not give reference examples, because that would take away from the main point. And the timing is not good enough to tell you about it.
About std::declval
std::declval<T>()
is not much to say, it returns a right-valued reference of type T.
But cppref is really confusing, what does declval really do? It is used to return a fake instance of a T object with a right-valued reference. In other words, it is equivalent to the compile-time state of objref as follows.
First, it is lexically and semantically equivalent to objref, which is an instance value of object T and has the type T&&; second, it is only used in non-valued situations; and third, it doesn’t really exist. What it means, in human terms, is that at compile time, you need a value object, but you don’t want it to be compiled as a binary entity, so you construct one virtually with declval, as if you had a temporary object on which you could apply operations, such as calling member functions, but since it is virtual, there is no such temporary object, so I call it pseudo-instances.
We often don’t really need the pseudo-instance directly from the declval, but more from the pseudo-instance to get the corresponding type description, i.e., T. So in general, declval is often surrounded by decltype computation, and trying to get T is the real goal: the declval is the only one we need.
|
|
As you can see, the pseudo-instance of A<int>
can “call” A’s member function t(), and then with the help of decltype we can get the return type of t() and use it to declare a specific variable a. Since the return type of t() is T, this variable declaration in the main() function statement in the main() function is actually equivalent to int a{};.
This example is meant to help you understand what declval actually means; the example itself is rather meaningless.
The power of declval
The core power of declval(expr)
is clear from the example above: it doesn’t really evaluate expr. So you don’t have to generate any temporary objects at expr, and no real computation occurs because the expression is complex. This is very useful for complex environments with metaprogramming.
The following page from a ppt also shows a use case where the expression does not have to be evaluated but only type.
But not only that, further power is derived from the undeclval of declval.
No default constructor
If a class does not define a default constructor, it can be troublesome in a metaprogramming environment. For example, the following decltype will not pass compilation.
because A() is not present.
But by using declval instead, you can get around the problem:
Pure Virtual Class
Sometimes metaprogramming on purely virtual base classes can be tricky, and it may be possible to bypass the problem of purely virtual base classes not being instantiated with the help of declval.
There is a corresponding reference in the first example decltype(std::declval<Base<int>>().t()) b{}; // = int b;
.
Refs
- C++ Type Deduction Introduction - hacking C++
- std::declval - cppreference.com
- decltype specifier - cppreference.com
Tricks
The above code involves some of the usual method, the following is a brief background, but also contains a little association extension.
Use a common abstract class as the base class
The system design of template classes may lead to bloat problems if the base class has a lot of code and data. One solution is to take a common base class and build templated base classes on top of it
|
|
This is written in such a way that generic logic (that doesn’t have to be generalized) can be abstracted out into base and avoids being left in base_t to bloat with generic instantiation.
How to put a pure virtual class in a container
By the way, we also talk about the containerization of pure virtual classes, abstract classes, and so on.
For class system design, we encourage pure virtualization of base classes, but such pure virtual base classes cannot be put into containers such as std::vector.
|
|
What to do?
It doesn’t make sense to use declval here, you should use smart pointers to decorate the abstract base class with.
Since we declare a non-generic base class base for the generic class base_t, it is also possible to use the
std::vector<base>
approach, but this requires you to extract all virtual interfaces into base, which would always leave some of the generic interfaces unextracted, so there is a chance that this approach will not work.
If you find virtual functions and their overloading so painful that you can’t stand them, you can consider CRTP, which is a very powerful compile-time polymorphism capability in the template class inheritance system.
In addition, it is possible to abandon the base class abstraction scheme and design the class system with the so-called runtime polymorphic trick.
Runtime Polymorphism
This is a runtime polymorphism coding technique provided by Sean Parent.
|
|
Animal::Interface is the abstract base class used for the class system, it is purely imaginary but does not affect the valid compilation and working of std::vector<Animal>
. Animal uses a simple transfer technique to map the interface of Animal::Interface ( Such as toString()), which is a bit like Pimpl Trick, but with a slight difference.
Postscript
In a nutshell, declval is specifically for situations where concrete objects cannot be instantiated.
std::declval<T>()
is also typically used for compile-time testing and other purposes, so we’ll explore that next time we have time, it’s too big a topic.