虚析构函数改变了decltype的行为

时间:2016-07-05 23:00:25

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

我创建了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;}));
}

请注意,如果析构函数被注释掉,则编译时没有错误。

意图是callableU类型的表达式,当使用()运算符调用时,返回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.

Here is an online version.

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

1 个答案:

答案 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;
};