哪种类型的特征表明该类型是memcpy可分配的?(元组,对)

alf*_*lfC 9 c++ variable-assignment memcpy type-traits c++11

我想知道我可以做什么类型的内省来检测可以通过原始内存副本分配的类型?

例如,据我所知,内置类型的内置类型元组和此类元组的元组将属于此类别.我的动机是,如果可能,我想传输原始字节.

T t1(...); // not necessarely default constructible 
T t2(...);

t1 = t2; // should be equivalent to std::memcpy(&t1, &t2, sizeof(T));
// t1 is now an (independent) copy of the value of t2, for example each can go out of scope independently
Run Code Online (Sandbox Code Playgroud)

如果赋值可以(原则上)替换为?,那么在编译类型中可以告诉什么type_trait组合type_traitsmemcpy

我想你会为我猜应该fullfil这个条件的类型和让我吃惊的是适合的行为是不是唯一的工作,std::is_trivially_assignable但是std::trivially_destructible.这在某种程度上是有意义的,但我很困惑为什么其他一些选项不能用于预期的情况.

我知道可能没有防弹方法,因为人们总是可以编写一个有效可记忆的类,不能被"检测"为memcopyable,但我正在寻找一个适用于简单直观案例的类.

#include<type_traits>
template<class T> using trait = 
    std::is_trivially_destructible
//  std::is_trivial
//  std::is_trivially_copy_assignable
//  std::is_trivially_copyable // // std::tuple<double, double> is not trivially copyable!!!
//  std::is_trivially_default_constructible
//  std::is_trivially_default_constructible
//  std::is_trivially_constructible
//  std::is_pod // std::tuple<double, double> is not pod!!!
//  std::is_standard_layout
//  std::is_aggregate
//  std::has_unique_object_representations
    <T>
;

int main(){
    static_assert((trait<double>{}), "");
    static_assert((trait<std::tuple<double, double>>{}), "");
    static_assert((not trait<std::tuple<double, std::vector<double>>>{}), "");
    static_assert((not trait<std::vector<double>>{}), "");
}
Run Code Online (Sandbox Code Playgroud)

当然,我认为元组应该是可记忆的,并不是基于标准,而是基于常识和实践.也就是说,因为这通常是好的:

std::tuple<double, std::tuple<char, int> > t1 = {5.1, {'c', 8}};
std::tuple<double, std::tuple<char, int> > t2;
t2 = t1;
std::tuple<double, std::tuple<char, int> > t3;
std::memcpy(&t3, &t1, sizeof(t1));
assert(t3 == t2);
Run Code Online (Sandbox Code Playgroud)

作为原理的证明,我实现了这一点.我添加了一些与大小相关的条件,以避免一些可能误导性的专业化std::tuple.

template<class T> 
struct is_memcopyable 
: std::integral_constant<bool, std::is_trivially_copyable<T>{}>{};

template<class T, class... Ts> 
struct is_memcopyable<std::tuple<T, Ts...>> : 
    std::integral_constant<bool, 
        is_memcopyable<T>{} and is_memcopyable<std::tuple<Ts...>>{}
    >
{};

template<class T1, class T2> 
struct is_memcopyable<std::pair<T1, T2>> : 
    std::integral_constant<bool, 
        is_memcopyable<T1>{} and is_memcopyable<T2>{}
    >
{};
Run Code Online (Sandbox Code Playgroud)

这是一个非常有限的解决方法,因为类如下:

struct A{ std::tuple<double, double> t; };

不幸的是,仍然会被报告为非易失性和不可复制的.

Ben*_*igt 8

事实上std::is_trivially_copyable,正确的测试允许memcpy用于制作新对象和修改现有对象.

虽然你可能会感到惊讶的是,对于你的直觉告诉你memcpy应该没问题的类型,这些返回是假的,但它们并不是说谎; 标准确实memcpy在这些情况下做出了不确定的行为.


在特定情况下std::pair,我们可以对出现的问题有所了解:

int main()
{
    typedef std::pair<double,double> P;
    std::cout << "\nTC:  " << std::is_trivially_copyable<P>::value;
    std::cout << "\nTCC: " << std::is_trivially_copy_constructible<P>::value;
    std::cout << "\nTCv: " << std::is_trivially_constructible<P, const P&>::value;
    std::cout << "\n CC: " << std::is_copy_constructible<P>::value;
    std::cout << "\n MC: " << std::is_move_constructible<P>::value;
    std::cout << "\nTCA: " << std::is_trivially_copy_assignable<P>::value;
    std::cout << "\nTCvA:" << std::is_trivially_assignable<P, const P&>::value;
    std::cout << "\n CA: " << std::is_copy_assignable<P>::value;
    std::cout << "\n MA: " << std::is_move_assignable<P>::value;
    std::cout << "\nTD:  " << std::is_trivially_destructible<P>::value;
}
Run Code Online (Sandbox Code Playgroud)

TC:0 TCC:1 TCv:1 CC:1 MC:1 TCA:0 TCvA:0 CA:1 MA:1 TD:1

显然,它不是轻易复制可分配的.1

pair赋值运算符是用户定义的,所以不是小事.


1我认为clang,gcc和msvc在这里都是错的,但是如果它满足std::_is_trivially_copy_assignable它就没有用,因为TriviallyCopyable要求复制构造函数(如果没有删除)是微不足道的,而不是TriviallyCopyAssignable特性.是的,他们是不同的.

如果不是用户提供的, X类的复制/移动赋值运算符是微不足道的......

VS

is_assignable_v<T, const T&>是真的is_assignable,并且已知的赋值 不会调用任何非平凡的操作.

pair<double, double>副本赋值运算符调用的操作是单个双精度的赋值,这微不足道的.

不幸的是,平凡可复制的定义依赖于第一个,它pair失败了.

一个简单的可复制类是一个类:

  • 其中每个复制构造函数,移动构造函数,复制赋值运算符和移动赋值运算符被删除或无关紧要,
  • 至少有一个未删除的复制构造函数,移动构造函数,复制赋值运算符或移动赋值运算符,以及
  • 有一个简单的,未删除的析构函数.