Variable template type vartype ()

I have the following code in which I expect decltype() will not work in the Derived class to get the run() method of the return-type base class method, since the base class does not have a default constructor.

 class Base { public: int run() { return 1; } protected: Base(int){} }; struct Derived : Base { template <typename ...Args> Derived(Args... args) : Base{args...} {} }; int main() { decltype(Derived{}.run()) v {10}; // it works. Not expected since // Derived should not have default constructor std::cout << "value: " << v << std::endl; //decltype(Base{}.run()) v1 {10}; // does not work. Expected since // Base does not have default constructor //std::cout << "value: " << v1 << std::endl; } 

I know that you can use declval<> to get member functions without passing constructors, but my question is why decltype works decltype . I tried to find something relevant in the C ++ standard, but did not find anything. Also tried several compilers (gcc 5.2, 7.1 and clang 3.8) and had the same behavior.

+8
c ++ c ++ 11 c ++ 14
source share
2 answers

Here is the magic of invaluable contexts ... and lies.

Actually trying to do something like:

 Derived d; 

it will be a compilation error. This is a compiler error, because in the process of computing Derived::Derived() we need to call Base::Base() , which does not exist.

But this is a constructor implementation detail. In the evaluated context, we certainly need to know this. But in an invaluable context, we don’t have to go that far. If you look at std::is_constructible<Derived>::value , you will see that it is true ! This is because you can create an instance of this constructor without arguments, because the implementation of this constructor goes beyond the immediate context of this instance. This lie - you can use the default construct Derived - allows you to use Derived{} in this context, and the compiler will gladly allow you to continue your fun journey and see that decltype(Derived{}.run()) is an int (which also doesn't calls the actual run call, so the body of this function also doesn't matter).

If you were honest in Derived constructor:

 template <typename ...Args, std::enable_if_t<std::is_constructible<Base, Args&...>::value, int> = 0> Derived(Args&... args) : Base(args...) { } 

Then decltype(Derived{}.run()) will not be able to compile, because now Derived{} poorly formed even in an invaluable context.

It's good to avoid lying to the compiler.

+12
source share

When the expression inside decltype includes a function template, the compiler only looks at the signature of the template function to determine if it is possible to instantiate the template if the expression was really in the evaluated context. The actual function definition is not used at this point.

(This is actually why std::declval can be used inside decltype , although std::declval has no definition at all.)

The template constructor has the same signature as just declared, but not yet defined:

 template <typename ...Args> Derived(Args&... args); 

When decltype compiler simply looks at this information and decides that Derived{} is a valid expression, an rvalue of type Derived<> . Part : Base{args...} is part of the template definition and is not used inside decltype .

If you need a compiler error, you can use something like this to make your constructor more "SFINAE-friendly", which means that information about whether the template specialization is really valid falls into the signature of the template.

 template <typename ... Args, typename Enable = std::enable_if_t<std::is_constructible<Base, Args&...>::value>> Derived( Args& ... args ) : Base{ args... } {} 

You can also change the constructor to avoid "too perfect forwarding." If you are running Derived x; Derived y{x}; Derived x; Derived y{x}; , specialization of the Derived(Derived&); template Derived(Derived&); will be better than implicit Derived(const Derived&); , and you end up trying to pass x to Base{x} instead of using the implicit Derived copy constructor.

+5
source share

All Articles