C++ 显式转换运算符vs get<;T>;()std::variant的函数

C++ 显式转换运算符vs get<;T>;()std::variant的函数,c++,variant,c++17,conversion-operator,C++,Variant,C++17,Conversion Operator,将std::variant类的一部分作为get()自由函数(第568页)之外的(或甚至代替)操作符重载集的显式转换(到可选)有什么缺点 模板 类变量 { 模板 显式运算符常量T&()常量&; 模板 显式算子T&(()&); 模板 显式运算符常量T&&()常量&; 模板 显式运算符T&&()&; }; 在某些情况下是否不安全?为什么我们需要免费的get()函数(即,“type”版本,而不是“index”版本),当variant公开了一个值语义时(从草稿中引用): 变量对象保存并管理值的生存期 让

std::variant
类的一部分作为
get()
自由函数(第568页)之外的(或甚至代替)操作符重载集的显式转换(到可选)有什么缺点

模板
类变量
{
模板
显式运算符常量T&()常量&;
模板
显式算子T&(()&);
模板
显式运算符常量T&&()常量&;
模板
显式运算符T&&()&;
};
在某些情况下是否不安全?为什么我们需要免费的
get()
函数(即,“type”版本,而不是“index”版本),当variant公开了一个值语义时(从草稿中引用):

变量对象保存并管理值的生存期

让一个变量显式地转换为alternative,但仍然可以从alternative隐式地构造,这还不够吗


我知道,接口的一致性是一件好事(我记得我们需要
get()
以及
get()
),但我认为更自然的方法是通过转换包含可选值,而不是对变量实例应用一些函数专门化。我可以想到一些缺点:

  • std::variant
    可以保存cv
    void
    ,但是返回cv
    void&
    和cv
    void&
    的转换函数是非法的,并且从不调用到cv
    void
    的转换函数([class.conv.fct]/1)
  • std::variant
    可以保存也可以从中构造的类型,例如:
    std::variant v;std::任何a{v}-在这种情况下会发生什么
此外,目前可以将返回
T
的函数转换为返回
std::variant
的函数,并期望编译器检测所有需要更改代码的情况;使用到
T
的转换功能,任何代码复制或绑定对
T
的引用的情况都会导致错误:

int f();
int i{f()};    // OK

// f() changes to:
std::variant<int, std::string> f();
int i{f()};    // can now throw std::bad_variant_access
intf();
int i{f()};//好啊
//f()更改为:
std::变体f();
int i{f()};//现在可以抛出std::bad\u variant\u访问

我不提议的主要原因如下:

  • 我没有
  • 可选的有一个
    显式运算符bool()
    是否有值()
  • 变量和可选变量应尽可能保持一致
但是如果有正确的论据,这肯定会在未来的修订版中改变。

1)我认为有一种情况是没有争议的,如果
std::variant
只有一种情况。(这可能发生在泛型代码中。)然后变量应该(隐式地,IMO)转换为单一类型

std::variant<double> v(5.3);
...
double d; d = v; // should be ok
我不认为因为
void
案例而没有该功能的借口是好的。 最坏的情况是,
void
是问题所在,而不是
variant
void
案件应由
monostate
处理(或将来由“常规
void
”)

总之,我支持你的想法,除了我会限制转换操作符

另外,我会返回一个副本,而不是一个引用(绝对不是一个非常量引用)。 原因是引用可能会使变量内容处于无效状态(与原始联合一样),或者如果变量稍后更改,则引用本身可能处于无效状态

[编辑:我收回这一点,引用一直都是无效的,这是可以的,例如,如果您引用向量的某个元素,并且稍后调整向量的大小。]

template<class... Types>
class variant
{
    template<class T>
    explicit operator T&() const{return std::get<T>(*this);}
    ...
};

template<class T>
class variant
{
    /*implicit*/ operator T&() const{return std::get<T>(*this);}
    ...
};
模板
类变量
{
模板
显式运算符T&()const{return std::get(*this);}
...
};
模板
类变量
{
/*隐式*/运算符T&()常量{return std::get(*this);}
...
};

由于缺少该功能,因此有一些临时解决办法,

a。派生自
std::variant
,并添加这些函数。从变体派生或否定似乎是一种常见的模式


b。重载变量情况下的函数,以便它可以直接获取变量,并立即通过
std::get(…)

调用原始函数。对于我来说,转换意味着“是”关系。get意味着“有”关系(“这个变量是什么?”是一个比“这个变量包含什么?”更糟糕的问题)。接口映射到它的底层抽象越自然越好(IMO)。我看不出矛盾之处。
get()
格式好吗?顺便说一句,我听不懂最后一句话。你可以分解它吗?@不,但在你真正尝试调用它之前,这没关系。引用cv
void
的转换运算符必须明确地从生成中排除,然后您可能会认为在实际转换为rvalue
void
时调用了它们。哦,您的意思是
(void)v
/
静态强制转换(v)
/
void(v)
。顺便说一句,它在现代编译器中有相应的警告(“未使用的成员函数”)。无论如何,将有一组cv ref限定的转换运算符重载,返回类似cv ref限定的基础值(例如
运算符const T&(()const&
),对于
T==void
,它们将在编译过程中导致硬错误,因此需要特殊处理以及其他(特殊)处理成员函数(转换c-tors和转换赋值运算符)。当然,变量应该(显式)转换为变量的一种情况,而不是任意类型。因此,
任何
的情况都可以处理。
void
的情况更加棘手,但在所有情况下都可以将其视为
monostate
std::variant<double, int> v(5.3);
...
double d{v}; // should be ok, but can throw
template<class... Types>
class variant
{
    template<class T>
    explicit operator T&() const{return std::get<T>(*this);}
    ...
};

template<class T>
class variant
{
    /*implicit*/ operator T&() const{return std::get<T>(*this);}
    ...
};