虚拟析构函数改变 decltype 的行为
Virtual destructor alters behavior of decltype
我创建了一个 header for optionally-lazy parameters (also visible in a GitHub repository). (This is not my first question based on the header。)
我有一个 base-class 模板和两个 derived-class 模板。 base-class 模板有一个带有 static_assert
的 protected
构造函数。此构造函数仅由特定的派生-class 调用。在 static_assert
中,我使用的是 decltype
.
真正奇怪的是 decltype
中的 a 名称的类型在某种程度上受我的基中是否存在虚拟析构函数的影响- class 模板。
这是我的 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
的表达式,当使用 ()
运算符调用时,returns 类型为 T
.没有 Base
中的虚拟析构函数,看起来这是正确评估的; with 虚拟析构函数,看起来 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
-ing 复制构造函数 也 触发此错误。
问题是当你声明析构函数时,隐式移动构造函数不会被声明,因为
(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
有用户声明的析构函数(默认没有关系)。
当MakeLazyInt
试图return构造Derived
对象时,它调用Derived
移动构造函数。
Derived
隐式声明的移动构造函数不会调用 Base
移动构造函数(因为它不存在),而是调用模板化的 Base(U&&)
构造函数。
问题来了,callable
参数不包含 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;
};
我创建了一个 header for optionally-lazy parameters (also visible in a GitHub repository). (This is not my first question based on the header。)
我有一个 base-class 模板和两个 derived-class 模板。 base-class 模板有一个带有 static_assert
的 protected
构造函数。此构造函数仅由特定的派生-class 调用。在 static_assert
中,我使用的是 decltype
.
真正奇怪的是 decltype
中的 a 名称的类型在某种程度上受我的基中是否存在虚拟析构函数的影响- class 模板。
这是我的 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
的表达式,当使用 ()
运算符调用时,returns 类型为 T
.没有 Base
中的虚拟析构函数,看起来这是正确评估的; with 虚拟析构函数,看起来 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
-ing 复制构造函数 也 触发此错误。
问题是当你声明析构函数时,隐式移动构造函数不会被声明,因为
(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
有用户声明的析构函数(默认没有关系)。
当MakeLazyInt
试图return构造Derived
对象时,它调用Derived
移动构造函数。
Derived
隐式声明的移动构造函数不会调用 Base
移动构造函数(因为它不存在),而是调用模板化的 Base(U&&)
构造函数。
问题来了,callable
参数不包含 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;
};