虚拟析构函数会改变 decltype 的行为
Virtual destructor alters behavior of decltype
我已经为可选的惰性参数创建了一个标头(在GitHub存储库中也可见)。(这不是我基于标题的第一个问题。
我有一个基类模板和两个派生类模板。基类模板具有一个带有static_assert
的protected
构造函数。此构造函数仅由特定的派生类调用。在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
复制构造函数也会触发此错误。
问题是当你声明析构函数时,隐式移动构造函数不会被声明,因为
(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;
};
- 在VS2010-VS2015下编译时,如何使用decltype作为较大类型表达式的LHS
- 变量没有改变?通过向量的函数调用
- C++decltype和圆括号-为什么
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 如何从"decltype()"获取函数参数的数量<funtion>?
- Confusion: decltype vs std::function
- 如何改变c++应用程序的视觉效果
- C++在不同线程中改变向量
- 如何访问和改变存储在矢量C++中的对象
- decltype(1, t) 应该是 l 值引用吗?(编译器不同意)
- 在C++行尾写一个分号或多个分号是否会改变任何内容
- 为什么字符串的 move() 会改变内存中底层数据的位置?
- 为什么 c++ 动态数组的大小没有改变?
- 为什么提升图库的 read_graphviz() 函数会改变节点的索引
- 在向量内更改变量的值不会改变其在向量外的值
- 为什么 GCC 不能假设 std::vector::size 在这个循环中不会改变?
- 改变或缩放两个正态分布以具有特定的相关系数
- 是否可以在不使用 decltype 的情况下推断先前定义的 extern 变量的类型
- 无效 f(int) 的模板和 decltype
- 虚拟析构函数会改变 decltype 的行为