考虑以下代码(问题如下):
#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;
}
您可以找到实时代码here。该代码具有一个有趣的属性:根据您使用--std=C++17
还是--std=C++14
选项进行编译,行为会发生变化(您可以在我的实时代码链接中尝试以下操作:编辑g ++调用{{ 1}}参数)。
使用--std
,您将获得以下输出:
--std=c++14
使用Non-default invoked: 0
Non-default invoked: 1
Default Invoked: 0
Non-default invoked: 1
0x0
,您会得到以下信息:
--std=C++17
对我来说,这种区别令人困惑。显然C ++ 17版本做正确的事,而C ++ 14是错误的。 C ++ 14版本正在调用Non-default invoked: 0
Non-default invoked: 1
0x7ffcdf02766c
和Composable
的默认构造函数(这是Type1
的最后一行来自的地方,因为{{1} }将此作为其0x0
构造函数参数的默认值)。但是,我看不到应该调用默认构造函数的任何地方。
此外,如果我完全注释掉Type1
的默认构造函数,则C ++ 17版本的功能与以前完全相同,而C ++ 14版本现在无法编译,抱怨缺少默认构造函数。如果希望通过不同的优化行为以某种方式解释差异,那么这一事实肯定会消除它(希望很小,因为观察到的差异在所有优化级别(包括0)都持续存在。
有人可以解释这种区别吗? C ++ 14行为是错误,还是某些我不理解的预期行为?如果C ++ 14行为在C ++ 14的规则内是正确的,那么有人可以解释一下默认构造函数调用的来源吗?
答案 0 :(得分:7)
保证复制省略。
此行:
auto comp = Composable<Type0, Type1>(&c, &i);
在C ++ 17中,这意味着与以下内容完全相同:
Composable<Type0, Type1> comp(&c, &i);
如果更改为该版本,则在C ++ 14和C ++ 17之间将看到相同的行为。但是,在C ++ 14中,这仍然是一个移动构造(或者,从技术上讲,如稍后所看到的,它是复制初始化)。但是在Composable
中,您没有隐式生成的move构造函数,因为您有一个用户声明的副本构造函数。结果,对于move构造,您的“非默认调用”构造函数模板在C ++ 14版本中被调用(比复制构造函数更匹配):
template<typename Arg, typename ... Args>
Composable(Arg&& arg, Args&& ... args) :
T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...)
在这里,Arg
是Composable<Type0, Type1>
,而Args
是一个空包。我们将委派给T0
(Type0
)的构造函数,转发整个Composable
(之所以起作用,是因为它公开地继承自Type0
,所以我们得到了隐式生成的move构造函数)和Composable<Type1>
的默认构造函数(因为args
为空)。
此构造函数模板不是真正的 正确的move构造函数-它根本不会初始化Type1
成员。而不是从右侧的Type1::i_
移动,而是调用默认构造函数Type1::Type1()
,这就是为什么最终使用0
的原因。
如果添加适当的move构造函数:
Composable(Composable&& other) = default;
然后,您将再次在C ++ 14和C ++ 17之间看到相同的行为。