decltype() 可变参数模板基类
decltype() variadic template base class
我有以下代码,我希望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
的隐式复制构造函数。
- 如何使基类的运算符对基类的可变参数数可见(请参阅下面的代码)?
- 命名参数习惯用法和(抽象)基类
- C++重载函数,一个采用基类的参数,另一个采用派生类的参数
- 如何允许模板参数中的类类型,仅当它有两个基类时
- 为什么我需要在成员发起器列表中重复基类的模板参数?
- 如何在不使用指针的情况下将派生类的对象作为参数传递给基类中的函数?
- C++17 使用驱动类常量作为基类构造函数的参数来初始化基类构造函数
- 根据参数数调用 mixin 基类的构造函数
- 派生类(构造函数具有参数)和基类(构造函数缺少参数)之间没有可行的转换
- 如何将子类作为函数的参数传递给期望基类,然后将该对象传递到指向这些抽象类对象的指针向量中?
- 在C++单元测试上下文中,抽象基类是否应将其他抽象基类作为函数参数
- C++ 如何使派生类自动获取基类参数
- 将派生类作为参数传递给方法,该方法是具有智能指针的基类
- 将函数上调整到基类参数的另一个
- 将派生类对象传递给具有 C++ 中基类参数的函数时,为什么要传递引用而不是值?
- 如何使用派生类中的虚拟函数,该函数在另一个具有基类参数的类中声明
- 将基类 2 参数构造函数调用到子类 1 参数构造函数中
- 指向友元函数中基类参数类型的指针
- 按值传递共享指针并接受作为基类参数是如何工作的
- 为带有基类参数的派生类参数调用函数重载