cpp日常


如前面所说,我有在做一个variant,已经基本没啥改动了。昨天把它和C++17提供的variant比较下,发现这两个的operator=的语义有差异,下面是测试代码

// x.cpp
#include <iostream>
#include <variant>
#include <boost/variant.hpp>
#include "variant.h"

struct T
{
  T() {}
  T(const T&) {}
 
  T(T&&) {}

  T& operator= (T&&)
  {
    return *this;
  }
  int x;
};

int main()
{
#ifdef STD
  std::variant<int, T, std::string, double> va = "233";
#elif defined(BOOST)
  boost::variant<int, T, std::string, double> va = "233";
#else
  nm::variant<int, T, std::string, double> va = "233";
#endif
  T t;
  va = t;
}

下面是编译输出
damn standard


我自己实现variant的operator=有两个,一个是

template<typename T> variant& operator= (const T& rhs); // #1

另一个是

template<typename T> variant& operator= (T&& rhs); // #2

以上两个都省略了SFINAE部分,其中#1是为const对象以及可转换临时对象设计的,#2则是为rvalue(包括prvalue和xvalue)设计的。其中#1相对简单,只需判断该类型T的可接受类型是否为当前的底层类型,然后决定是重新构造还是对底层对象进行赋值,而#2则要复杂一些,因为T&&是一个universal reference,因此必然会发生引用折叠,即使如此,也是遵循#1的思路,不同的是,如果 rhs 的推断类型是右值的话需要使用move assignment


正是这个很自然的想法与C++17的variant有了冲突。从上面代码来看,即使不用variant直接写下这样的代码

T t, tt;
tt = t; // error: use of deleted function ‘constexpr T& T::operator=(const T&)’

如果你不知道为什么会编译出错,请查看Effective Modern C++ item 17 page 111
同样会出现编译错误。然而,C++17的variant却能顺利地通过编译!!


下面是今年3月的C++17标准的草案的部分截图

variant.assign1
variant.assign2
结合上面的代码以及标准的定义,发现,标准的实现先是尝试判断给定的T是否是当前底层对象的类型,如果是,那么进行赋值,如果不是且发现可以根据T构造构造一个当前的底层对象,那么就调用底层对象构造函数(上面代码没有编译错误正是如此),否则构造一个临时的variant再调用variant的构造函数。写成伪代码大概是这样

if type of rhs is underlying object type
then
  get underlying object and assign rhs to it
else if underlying object is constructible from rhs
then
  delete this
  placement new with current underlying type with constructor argument rhs
else
then
  delete this
  new(this) variant(rhs)
endif

不能说C++17标准的这种做法不对,也不能说我自己的以及boost的variant的做法是不对的。但是,就上面的测试而言,C++17标准的做法是不合理的! 一定要解释为啥不合理的话,就是,原本一个不可赋值的对象在一个赋值函数中“被赋值”了,而这样的赋值并没有遵循赋值的语义

最后,我的variant并不需要支持C++17的编译器,因此,根本没有必要和C++17的variant进行对比啊。。。



转载请注明:Serenity » cpp日常

上一篇

下一篇