像 Initialization 一样聚合,无需多余调用构造函数

Aggregate like Initialization without superfluous calls of constructors

提问人:tommsch 提问时间:2/7/2022 最后编辑:Ted Lyngmotommsch 更新时间:2/8/2022 访问量:304

问:

我正在尝试主要编写 .但是,我对聚合初始化的规则(以及其他一些小细节)不是很满意,因此不想使用它。std::array

我不喜欢聚合启动的事情:

  • 列表中的元素少于必要的元素,不会产生错误,这对于聚合的聚合尤其奇怪,例如
    struct A { int x,y; };
    std::array< A, 2 > arr{1,2,3};
    // produces A[0].x = 1, A[0].y = 2, A[1].x = 1, A[1].y = 0
    
  • 用户仍可调用私有/已删除的 CTOR
    class B { B() = delete; };
    B{};  // no compile time error
    

到目前为止,我所拥有的如下:

#include <cstddef>
#include <type_traits>
#include <utility>

template< typename T, typename ... Ts >
inline constexpr bool areT_v = std::conjunction_v< std::is_same<T, Ts> ... >;  

namespace ttt {
    template< typename T, int N >
    struct array
    {
        template< typename ... L > 
        array( L && ... lst ) : content{ std::forward< L >( lst ) ... } {
            static_assert( sizeof...(L) == N || sizeof...(L)==0, ""  );
            static_assert( areT_v<T,L...>, "" );
        }
        array() = default;  // not sure whether I need this one
    
        using ContentType = typename std::conditional< N>=1 , T, char >::type;
        ContentType content[ N>=1 ? N : 1];

        /* member functions */
    };
}

实际上,除了以下事实之外,我对这种实现非常满意。使用时,我有更多的 ctor 调用,然后使用 .例如:ttt::arraystd::array

#include <array>
#include <iostream>

struct CLASS {
    int x, y;
    CLASS() { 
        std::cout << "def-ctor" << std::endl; 
    }
    CLASS(int x_,int y_) : x(x_), y(y_) { 
        std::cout << "xy-ctor" << std::endl; 
    }
    CLASS( CLASS const & other ) : x(other.x), y(other.y) {
        std::cout << "copy-ctor" << std::endl; 
    }
};

int main() {
    std::array< CLASS, 1 > stda{CLASS{1,2}};  // calls xy-ctor
    ttt::array< CLASS, 1 > ttta{CLASS{1,2}};  // calls xy-ctor AND copy-ctor
}

这似乎与聚合初始化的原因有关(因为它是聚合),但事实并非如此。 这让我想到了一个问题,编译器是否允许在这里做某事(省略一个 ctor 调用),我不被允许做?std::arrayttt::array

而且,有没有办法解决这个问题?

C++ C++14 聚合初始化

评论

7赞 user7860670 2/7/2022
“用户仍可调用私有/已删除的 CTOR”已在 C++20 gcc.godbolt.org/z/9b9Wvn3fj 中修复
0赞 Igor Tandetnik 2/7/2022
从某种意义上说,聚合对编译器是透明的;编译器可以直接初始化其成员,无需调用构造函数,可以执行一些特殊操作。由于你的类不是一个聚合,调用代码别无选择,只能构造一个临时的,并将其传递给构造函数(然后构造函数别无选择,只能将其复制或移动到 );它不能绕过构造函数并将实例直接写入 Array。我不认为你可以在不进行聚合的情况下避免复制(这会破坏这一点)。CLASSttt::arraycontentCLASScontentttt::array
0赞 Marek R 2/8/2022
编译器将问题 1 检测为警告:godbolt.org/z/a6Wobrsqe。IMO编译器的正确配置是更好的方法,然后创建新的。array

答:

1赞 Ted Lyngmo 2/8/2022 #1

您可以打包应该用于构造元素的参数,并延迟实际对象的创建,直到需要初始化。contentstd::tuplecontent

大纲:

template <class T, std::size_t N>
struct array {
    template<class... Ts>
    constexpr array(Ts&&... args) :
        content{to_t(std::forward<Ts>(args),
                     std::make_index_sequence<std::tuple_size<Ts>::value>())...}
    {
        static_assert(sizeof...(Ts) == N, "wrong number of elements");
    }

private:
    // create one T from a tuple
    template<class... Ts, std::size_t... Idx>
    T to_t(std::tuple<Ts...>&& in, std::index_sequence<Idx...>) {
        return T{std::forward<Ts>(std::get<Idx>(in))...};
    }

    T content[N];
};

要放入的示例类型:array

struct Foo {
    Foo() : a(0.), b(0) { std::cout << "Foo::Foo()\n"; }
    explicit Foo(double A, int B) : a(A), b(B) { std::cout << "Foo::Foo(double, int)\n"; }
    Foo(const Foo& o) : a(o.a), b(o.b) { std::cout << "Foo(const Foo&)\n"; }
    Foo(Foo&& o) : a(o.a), b(o.b) { std::cout << "Foo(Foo&&)\n"; }
    ~Foo() { std::cout << "Foo::~Foo() {" << a << ", " << b << "}\n"; }

private:
    double a;
    int b;
};

然后,你可以在不复制/移动你的 :s 的情况下创建你的 (在 C++ 17 中保证,在 C++ 14 中复制/移动省略,所以不能保证但可能)通过将它们打包成 s 来创建。打字有点麻烦。希望其他人对如何解决这个问题有一个想法。arrayTtuple

int main() {
    Foo f{3.3, 3};
    array<Foo, 4> x(std::forward_as_tuple(1.1, 1),
                    std::forward_as_tuple(),            // default constructor
                    std::forward_as_tuple(2.2, 2),
                    std::forward_as_tuple(std::move(f)) // move constructor
                   );
}

演示
您可以添加编译器选项,以查看在 C++14 模式下关闭该优化时它将移动元素。
-fno-elide-constructors

评论

0赞 tommsch 2/8/2022
我是否正确理解这是类似于 ,但对于一次给出的所有参数?emplace_back
0赞 Ted Lyngmo 2/8/2022
@tommsch 是的,这就是我的目标。我认为它可以通过助手功能变得更好。我会更多地考虑这个问题,如果我想出任何东西,我会更新答案