【问题标题】:Virtual destructor alters behavior of decltype虚拟析构函数改变 decltype 的行为
【发布时间】:2016-11-07 22:08:32
【问题描述】:

我创建了一个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&lt;T&gt;(据我所知,这没有任何意义)。

这是 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.

Here is an online version.

编辑:=delete-ing 复制构造函数会触发此错误。

【问题讨论】:

  • 我无法告诉您奇怪的虚拟析构函数错误,但我看到 Derived(U&amp;&amp; callable) 采用 r 值引用而不是通用引用。这是故意的吗?
  • 您能否在示例中添加一些输出,以显示它应该做什么。由于最后的运算符 T();,在您的示例中 TakesWrappedInt 似乎为零。
  • @GuyGreer 不,这不是故意的。是不是因为一旦模板专门化了,U 就不再是模板类型了?
  • @JohanLundberg 没错;这只是一个 MCVE,整数的值不是问题的一部分。如需更完整的上下文,请单击第一句中的链接。
  • @GuyGreer ....虽然实际上,按照预期用途,callable 可能应该是一个右值参考。

标签: c++ templates c++14 decltype virtual-destructor


【解决方案1】:

问题是当你声明析构函数时,隐式移动构造函数不会被声明,因为

(N4594 12.8/9)

如果类 X 的定义没有显式声明移动构造函数,则将隐式声明一个非显式构造函数 声明为默认当且仅当

...

  • X 没有用户声明的析构函数

Base 有用户声明的析构函数(它是默认的没关系)。

MakeLazyInt 尝试返回构造的Derived 对象时,它调用Derived 移动构造函数。

Derived 隐式声明的移动构造函数不调用 Base 移动构造函数(因为它不存在),而是你的模板化 Base(U&amp;&amp;) 构造函数。

问题来了,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;
};

【讨论】:

  • 啊啊啊啊。根据错误消息,我知道该规则,但没有看到它是如何应用的。谢谢你。你知道 C++17 的保证复制省略是否可以避免这个问题?
  • @KyleStrand 那么基于第二条规则在函数调用中,如果return语句的操作数是prvalue并且函数的返回类型与prvalue的类型相同。 ,我认为编译器会省略移动构造函数,但我不确定为什么编译器在这种情况下没有省略它。
  • @PcAF Copy-ellision 是一种优化,因此编译器可能会认为它是一个错误,即使副本 被省略为具有如果副本被忽略的构造会失败没有省略。这就是为什么我怀疑有保证的复制省略可以解决这个问题。
  • @KyleStrand 你是对的。我有tested 它,编译器似乎优化了它。我认为保证复制省略将解决这个问题:在那些情况下复制省略不保证,如果它发生并且复制/移动构造函数没有被调用,它必须存在且可访问(就好像根本没有进行优化一样),否则程序格式不正确。 source
猜你喜欢
  • 2015-08-22
  • 2013-07-06
  • 2011-08-12
  • 2012-04-13
  • 2012-04-18
  • 2015-11-12
  • 2017-03-16
  • 2012-12-20
相关资源
最近更新 更多