Virtual destructor changes decltype behavior

I created a header for optionally lazy parameters (also visible on GitHub repository). (This is not my first title based question .)

I have a base class template and two derived class templates. The base class template has a protected constructor with static_assert . This constructor is called only by a specific derived class. Inside static_assert I use decltype .

It is really strange that the type of the name inside decltype somehow depends on whether a virtual destructor exists in my base class template.

Here is my MCVE:

 #include <type_traits> #include <utility> template <typename T> class Base { protected: template <typename U> Base(U&& callable) { static_assert( std::is_same< typename std::remove_reference<decltype(callable())>::type, T >::value, "Expression does not evaluate to correct type!"); } public: virtual ~Base(void) =default; // Causes error virtual operator T(void) =0; }; template <typename T, typename U> class Derived : public Base<T> { public: Derived(U&& callable) : Base<T>{std::forward<U>(callable)} {} operator T(void) override final { return {}; } }; void TakesWrappedInt(Base<int>&&) {} template <typename U> auto MakeLazyInt(U&& callable) { return Derived< typename std::remove_reference<decltype(callable())>::type, U>{ std::forward<U>(callable)}; } int main() { TakesWrappedInt(MakeLazyInt([&](){return 3;})); } 

Note that if the destructor is commented out, it compiles without errors.

The goal of callable is an expression of type U , which when called with the operator () returns something of type T Without a virtual destructor in Base , it seems like this is being evaluated correctly; with a virtual destructor, it seems that the type of callabele is Base<T> (which, as far as I can tell, does not make sense).

Here's the g ++ 5.1 error message:

 recursive_lazy.cpp: In instantiation of 'Base<T>::Base(U&&) [with U = Base<int>; T = int]': recursive_lazy.cpp:25:7: required from 'auto MakeLazyInt(U&&) [with U = main()::<lambda()>]' recursive_lazy.cpp:48:47: required from here recursive_lazy.cpp:13:63: error: no match for call to '(Base<int>) ()' typename std::remove_reference<decltype(callable())>::type, T 

Here is the Clang ++ 3.7 error message:

 recursive_lazy.cpp:13:55: error: type 'Base<int>' does not provide a call operator typename std::remove_reference<decltype(callable())>::type, T ^~~~~~~~ recursive_lazy.cpp:25:7: note: in instantiation of function template specialization 'Base<int>::Base<Base<int> >' requested here class Derived : public Base<T> ^ 1 error generated. 

Here is the online version.

EDIT: =delete -repeat the copy constructor also causes this error.

+7
c ++ templates c ++ 14 decltype virtual-destructor
source share
1 answer

The problem is that when the destructor is declared, the implicit move constructor will not be declared, because

(N4594 12.8 / 9)

If the definition of class X does not explicitly declare the move constructor, the implicit will be implicitly defaulted if and only if

...

  • X does not have a user declared destructor

Base has a user-declared destructor (it doesn't matter what it is by default).

When MakeLazyInt tries to return a constructed Derived object, it calls the Derived move constructor.

Derived implicitly declared move constructor does not call the Base move constructor (because it does not exist), but your templated Base(U&&) constructor Base(U&&) .

And here is the problem, the callable parameter callable not contain the called object, and the Base object, which actually does not contain operator () .

To solve the problem, simply declare the move constructor inside Base :

 template <typename T> class Base { protected: template <typename U> Base(U&& callable) { static_assert( std::is_same< typename std::remove_reference<decltype(callable())>::type, T >::value, "Expression does not evaluate to correct type!"); } public: virtual ~Base(void) =default; // When declared, no implicitly-declared move constructor is created Base(Base&&){} //so we defined it ourselves virtual operator T(void) =0; }; 
+10
source share

All Articles