Sme*_*eey 2 c++ templates variadic-templates c++14 c++17
请考虑以下代码(以下问题):
#include <iostream>
struct Type0
{
Type0(char* c)
{}
};
struct Type1
{
Type1(int* i=nullptr) : i_(i)
{}
Type1(const Type1& other) = default;
int* i_;
};
template <typename ...>
struct Composable;
template <typename T0, typename ... T>
struct Composable<T0, T...> : T0, Composable<T...>
{
Composable()
{
std::cout << "Default Invoked: " << sizeof...(T) << std::endl;
}
Composable(const Composable& other) = default;
template<typename Arg, typename ... Args>
Composable(Arg&& arg, Args&& ... args) :
T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...)
{
std::cout << "Non-default invoked: " << sizeof...(T) << std::endl;
}
};
template <>
struct Composable<>{};
int main()
{
int i=1;
char c='c';
auto comp = Composable<Type0, Type1>(&c, &i);
std::cout << comp.i_ << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
你可以在这里找到实时代码.这段代码有一个有趣的属性:取决于你是用--std=C++17or --std=C++14选项编译它,行为会改变(你可以在我的实时代码链接中尝试这个:编辑--std左下角的g ++调用参数).
使用--std=c++14,您将获得以下输出:
Non-default invoked: 0
Non-default invoked: 1
Default Invoked: 0
Non-default invoked: 1
0x0
Run Code Online (Sandbox Code Playgroud)
有了--std=C++17,你得到这个:
Non-default invoked: 0
Non-default invoked: 1
0x7ffcdf02766c
Run Code Online (Sandbox Code Playgroud)
对我来说,这种差异令人困惑.很明显,C++ 17版本正在做正确的事情,而C++ 14是错误的.C++ 14版本调用两者的默认构造函数Composable和(从它)Type1(这是0x0输出的最后一行来自的地方,因为Type1它提供了它作为其i构造函数参数的默认值).但是,我没有看到任何应该调用默认构造函数的地方.
此外,如果我完全注释掉默认构造函数Composable,那么C++ 17版本与之前完全相同,而C++ 14版本现在无法编译,抱怨缺少默认构造函数.如果有任何希望差异被不同的优化行为以某种方式解释,这个事实肯定会杀死它(希望无论如何都很小,因为观察到的差异在所有优化级别都持续存在,包括0).
谁能解释这种差异?C++ 14行为是一个错误,还是一些我不理解的行为?如果C++ 14的行为在C++ 14的规则中是正确的,有人可以解释一下默认构造函数调用的来源吗?
保证副本省略.
这一行:
auto comp = Composable<Type0, Type1>(&c, &i);
Run Code Online (Sandbox Code Playgroud)
在C++ 17中,这意味着完全相同:
Composable<Type0, Type1> comp(&c, &i);
Run Code Online (Sandbox Code Playgroud)
如果你改成这个版本,你会看到C++ 14和C++ 17的相同行为.但是,在C++ 14中,这仍然是一个移动结构(或者,技术上更正确,你会在一分钟内看到,复制初始化).但是Composable,你没有隐式生成的移动构造函数,因为你有一个用户声明的复制构造函数.因此,对于移动构造,在C++ 14版本中调用"非默认调用"构造函数模板(它比复制构造函数更好):
template<typename Arg, typename ... Args>
Composable(Arg&& arg, Args&& ... args) :
T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...)
Run Code Online (Sandbox Code Playgroud)
在这里,Arg是Composable<Type0, Type1>和Args是一个空包.我们委托给T0(Type0)的构造函数,转发整个Composable(这是因为它Type0公开继承,所以我们得到隐式生成的移动构造函数)和Composable<Type1>默认构造函数(因为它args是空的).
此构造模板是不是真正正确的举动构造-它并没有结束初始化Type1所有成员.而不是从Type1::i_您正在调用的右侧移动Type1::Type1()默认构造函数,这就是您最终的结果0.
如果添加适当的移动构造函数:
Composable(Composable&& other) = default;
Run Code Online (Sandbox Code Playgroud)
然后你会再次看到C++ 14和C++ 17之间的相同行为.