我创建了header for optionally-lazy parameters(在GitHub repository中也可见)。 (这是not my first question based on the header。)
我有一个基类模板和两个派生类模板。基类模板具有protected
构造函数,其static_assert
。此构造函数仅由特定的派生类调用。 static_assert
内部我正在使用decltype
。
奇怪的是,decltype
中的名称的类型在某种程度上受到我的基类模板中是否存在虚拟析构函数的影响。
这是我的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;}));
}
请注意,如果析构函数被注释掉,则编译时没有错误。
意图是callable
是U
类型的表达式,当使用()
运算符调用时,返回T
类型的内容。如果没有Base
中的虚拟析构函数,则可以正确评估此值; 使用虚拟析构函数,callabele
的类型似乎是Base<T>
(据我所知,这没有任何意义)。
这是G ++ 5.1的错误消息:
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
这是Clang ++ 3.7的错误消息:
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.
编辑: =delete
- 复制构造函数也会触发此错误。
答案 0 :(得分:10)
问题在于,当声明析构函数时,不会声明隐式移动构造函数,因为
(N4594 12.8 / 9)
如果类X的定义没有显式声明移动构造函数,则隐式地使用非显式构造函数 当且仅当
声明为默认值...
- X没有用户声明的析构函数
Base
具有用户声明的析构函数(默认值无关紧要)。
当MakeLazyInt
尝试返回构造的Derived
对象时,它会调用Derived
移动构造函数。
Derived
隐式声明的移动构造函数不会调用Base
移动构造函数(因为它不存在),而是模板化的Base(U&&)
构造函数。
这就是问题,callable
参数不包含可调用对象,但Base
对象实际上不包含operator ()
。
要解决此问题,只需在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;
};