decltype()可变参数模板基类

Adv*_*ere 9 c++ c++11 c++14

我有以下代码,我希望decltype()Derived类上不能使用run()基类方法返回类型,因为基类没有默认构造函数.

class Base
{
  public:
    int run() { return 1; }

  protected:
    Base(int){}
}; 

struct Derived : Base
{
  template <typename ...Args>
  Derived(Args... args) : Base{args...}
  {}
};

int main()
{
  decltype(Derived{}.run()) v {10}; // it works. Not expected since
                                    // Derived should not have default constructor
  std::cout << "value: " << v << std::endl;

  //decltype(Base{}.run()) v1 {10}; // does not work. Expected since 
                                    // Base does not have default constructor
  //std::cout << "value: " << v1 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

我知道你可以使用declval<>获取成员函数而无需通过构造函数,但我的问题是为什么decltype在这里工作.我试图在C++标准中找到相关的东西,但没有找到任何东西.还尝试了多个编译器(gcc 5.2,7.1和clang 3.8)并具有相同的行为.

Bar*_*rry 13

看到无价值背景的魔力......和说谎.

实际上尝试做类似的事情:

Derived d;
Run Code Online (Sandbox Code Playgroud)

将是一个编译错误.这是一个编译器错误,因为在评估过程中Derived::Derived()我们必须调用Base::Base(),这是不存在的.

但这是构造函数实现的细节.在评估的背景下,我们当然需要知道这一点.但在未评估的背景下,我们不需要走得那么远.如果你检查std::is_constructible<Derived>::value,你会看到它true!这是因为您可以在没有参数的情况下实例化该构造函数 - 因为该构造函数的实现超出了该实例化的直接上下文.这个谎言-你可以通过缺省方式构造Derived-允许你使用Derived{}在这种情况下,编译器会很乐意让你去你的快乐的方式,看到decltype(Derived{}.run())int(这也实际上不涉及调用run,从而使函数体同样无关紧要).

如果你在Derived构造函数中诚实:

template <typename ...Args,
    std::enable_if_t<std::is_constructible<Base, Args&...>::value, int> = 0>
Derived(Args&... args) : Base(args...) { }
Run Code Online (Sandbox Code Playgroud)

然后decltype(Derived{}.run())将无法编译,因为Derived{}即使在未评估的背景下,现在也是不正确的.

避免欺骗编译器是件好事.


asc*_*ler 5

当内部表达式decltype涉及函数模板时,编译器仅查看模板函数的签名,以确定如果表达式确实位于计算上下文中,是否可以实例化模板.此时不使用该函数的实际定义.

(事实上​​,这就是为什么std::declval可以在里面使用,decltype即使std::declval根本没有定义.)

您的模板构造函数具有相同的签名,就像简单声明但尚未定义一样:

template <typename ...Args>
Derived(Args&... args);
Run Code Online (Sandbox Code Playgroud)

在处理时decltype,编译器只查看那么多信息并决定Derived{}是一个有效的表达式,一个类型的右值Derived<>.该: Base{args...}部分是模板定义的一部分,不会在内部使用decltype.

如果你想在那里编译错误,你可以使用这样的东西使你的构造函数更"SFINAE友好",这意味着关于模板的特化是否实际有效的信息被放入模板签名中.

template <typename ... Args, typename Enable =
    std::enable_if_t<std::is_constructible<Base, Args&...>::value>>
Derived( Args& ... args ) : Base{ args... }
{}
Run Code Online (Sandbox Code Playgroud)

您可能还想修改构造函数以避免"过于完美的转发".如果这样做Derived x; Derived y{x};,将模板特Derived(Derived&);会比隐更好的匹配Derived(const Derived&);,而你最终会试图传递xBase{x}而不是使用隐含的拷贝构造函数Derived.