C++ 14和C++ 17之间的默认构造函数调用差异

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的规则中是正确的,有人可以解释一下默认构造函数调用的来源吗?

Bar*_*rry 7

保证副本省略.

这一行:

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)

在这里,ArgComposable<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之间的相同行为.