I've created a header for optionally-lazy parameters (also visible in a GitHub repository). (This is not my first question based on the header.)
I have a base-class template and two derived-class templates. The base-class template has a protected
constructor with a static_assert
. This constructor is only called by a particular derived-class. Inside of the static_assert
I'm using a decltype
.
The really bizarre thing is that the type of a name inside the decltype
is somehow affected by the whether or not there is a virtual destructor in my base-class template.
Here's 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, this compiles without error.
The intent is for callable
to be an expression of type U
that, when called with the ()
operator, returns something of type T
. Without the virtual destructor in Base
, it appears that this is evaluated correctly; with the virtual destructor, it appears that callabele
's type is Base<T>
(which, as far as I can tell, makes no sense).
Here's G++ 5.1's 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's Clang++ 3.7's 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.
EDIT: =delete
-ing the copy-constructor also triggers this error.
The problem is that when you declare destructor, implicit move constructor won't be declared, because
(N4594 12.8/9)
If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if
...
- X does not have a user-declared destructor
Base
has user-declared destructor (it doesn't matter that it's defaulted).
When MakeLazyInt
tries to return constructed Derived
object, it calls Derived
move constructor.
Derived
implicitly-declared move constructor doesn't call Base
move constructor (because that doesn't exist), but rather your templated Base(U&&)
constructor.
And here's the problem, callable
parameter doesn't contain callable object but Base
object, which really doesn't contain operator ()
.
To solve the problem simply declare 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;
};