decltype() 可变参数模板基类

decltype() variadic template base class

本文关键字:基类 参数 变参 decltype      更新时间:2023-10-16

我有以下代码,我希望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;
}

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

看看未评估上下文的魔力......和撒谎。

实际上尝试做这样的事情:

Derived d;

将是一个编译错误。这是一个编译器错误,因为在评估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...) { }

然后decltype(Derived{}.run())将无法编译,因为现在即使在未评估的上下文中Derived{}格式也不正确。

最好避免对编译器撒谎。

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

(事实上,这就是为什么std::declval可以在decltype内部使用,即使std::declval根本没有定义。

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

template <typename ...Args>
Derived(Args&... args);

在处理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... }
{}

您可能还希望修改构造函数以避免"过于完美的转发"。 如果你这样做Derived x; Derived y{x};,模板专用化Derived(Derived&);将比隐式Derived(const Derived&);更好地匹配,并且你最终会尝试将x传递给Base{x}而不是使用Derived的隐式复制构造函数。

相关文章: