【现代C++】再谈Structured Bindings

news/2024/7/3 12:24:40 标签: c++, 编程语言, java, go, 多态

(点击上方公众号,可快速关注)

劳动节快乐!打工人加油!

上一篇文章《使代码简洁的两种解包方式》介绍了两种代码简洁工具,尤其推荐Structured Bindings,因为它能绑定各种类型的成员。那篇文章介绍的用法基本能覆盖我们的日常应用了,本篇主要查缺补漏,着重介绍自定义类型怎么支持Structured Bindings特性。

主要参考资料是C++20语言规范草案(N4868),草案内容与正式版基本一致,而且完全免费,所以这是大部分C++程序员的参考资料。我已经将N4868上传到百度网盘,可以在公众号里发送“n4868”获取下载地址。

弃用volatile修饰

volatible修饰绑定变量的用法已经废弃。实际上不止structured bindings,其他地方也越来越不待见这个关键字,比如前缀后缀的++-- 操作符也废弃了对volatile变量的操作。

go">struct linhenykus { short forelimb; };
void park(linhenykus alvarezsauroid) {
    volatile auto [what_is_this] = alvarezsauroid; // deprecated
    // ...
}

类数据成员的一些限制

除了保证数据成员是public外,还有一些其他的限制规则。

不能绑定静态数据成员

结构绑定只能绑定非静态成员,下面给一个完整的示例:

go">#include <string>

class Object
{
    static int newCount;

    std::string name;
    std::string hashCode;
};
int Object::newCount = 0;

int main()
{
    Object obj{"person", "xyz"};

    auto &[name, hashCode] = obj; // 只能绑定非静态成员,newCount没法绑定

    return 0;
}

也能看出来,非静态成员的数量跟绑定变量的数量是一致的,否则会出错;而且,绑定顺序跟数据成员的声明顺序是一致的

类不能包含匿名的联合(union)成员

看下面的代码示例,没法对Edge类对象结构绑定。编译器会在编译阶段推导出绑定变量的类型,这样的好处是不会影响程序的运行效率。若类中包含匿名联合成员,编译阶段没法确定绑定变量的类型。在技术上,要确定每个绑定的类型是可能的,这需要编译器记录更多的信息,在运行时进行判断,势必会影响程序的运行效率,违背了C++零抽象的原则。

go">struct Edge
{
    std::string name;
    union //包含匿名联合成员的类示例不能结构绑定
    {
        int     n_weight;
        double  d_weight;
    };
};

数据成员归属限制

“数据成员归属”指这个数据是哪个类引入的,可能并不准确,实在想不出其他词 ,有更恰当的词欢迎在评论区指正。

看下面这个例子,会出错:

go">struct Person
{
    std::string name;
};
struct Chinese : Person
{
    bool bCPC;
};

Chinese ch;
ch.name="me";
ch.bCPC=false;

auto [name,bCPC] = ch; //出错

ch对象是Chinese类型,它的name成员归属为基类,bCPC归属为自己。这种继承关系比较常见,但结构绑定不能支持。

语言规范对数据成员归属描述如下:

all of E’s non-static data members shall be direct members of E or of the same base class of E, well-formed when named as e .name in the context of the structured binding

非静态成员,要么是“类型的直接成员”,要么是“基类的直接成员”,不允许例子中这样的混合情况,既有自己的数据成员,还有基类的数据成员。根据标准的限制规则,我们有两种改正方式:

数据成员归属为基类

go">struct Person
{
    std::string name;
    bool bCPC;
};

struct Chinese : Person {};

Chinese ch;
ch.name="me";
ch.bCPC=false;

auto [name,bCPC] = ch;

数据成员都为Chinese的直接成员

go">struct Person {};

struct Chinese : Person
{
    std::string name;
    bool bCPC;
};

Chinese ch;
ch.name="me";
ch.bCPC=false;

auto [name,bCPC] = ch;

自定义类型

内置的类型成员的限制都可以通过自定义方式破解。自定义实际就是模拟元组的行为,需要实现自己的get<>() 、tuple_size 、tuple_element。示例如下:

go">#include <iostream>
#include <string>
#include <tuple>

class Person
{
public:
    Person(const std::string& name) 
        : sName(name) {}
    std::string& getName() 
    { return sName; }
private:
    std::string sName;
};
struct Chinese : Person
{
public:
    Chinese(const std::string& name, bool cpc) 
        : Person(name), bCPC(cpc){}
    bool getCPC() { return bCPC; }

public:
    template <std::size_t N>
    decltype(auto) get() 
    {
        if constexpr (N == 0) 
            return getName();
        else if constexpr (N == 1) 
            return getCPC();
    }
private:
    bool bCPC;
};

namespace std {
    template<>
    struct tuple_size<Chinese>
        : std::integral_constant<std::size_t, 2> {};

    template<std::size_t N>
    struct tuple_element<N, Chinese> {
        using type = 
        decltype(std::declval<Chinese>().get<N>());
    };
}

int main()
{
    Chinese ch("me", false);
    auto [name,bCPC] = ch;

    return 0;
}

tuple_sizetuple_element需要实现在std命名空间内,get函数可以定义在类型内,也可以在类型外,上例是在类型内定义的。我们将上例改写为get在类型外定义:

go">#include <iostream>
#include <string>
#include <tuple>

class Person
{
public:
    Person(const std::string& name) : sName(name) {}
    std::string& getName() { return sName; }
private:
    std::string sName;
};
struct Chinese : Person
{
public:
    Chinese(const std::string& name, bool cpc) 
        : Person(name), bCPC(cpc){}
    bool getCPC() { return bCPC; }

private:
    bool bCPC;
};

template <std::size_t N>
decltype(auto) get(Chinese& ch) 
{
    if      constexpr (N == 0) 
        return ch.getName();
    else if constexpr (N == 1) 
        return ch.getCPC();
}

namespace std {
    template<>
    struct tuple_size<Chinese>
        : std::integral_constant<std::size_t, 2> {};

    template<> 
    struct tuple_element<0,Chinese> 
    { using type = std::string; };
    template<> 
    struct tuple_element<1,Chinese> 
    { using type = bool; };
}

int main()
{
    Chinese ch("me", false);
    auto& [name,bCPC] = ch;

    return 0;
}

有两点需要特别注意:

  1. tuple_element用了最原始的手段实现,这是因为get方法已经不是类型的成员了,所以这里枚举了各个成员的类型。

  2. get函数的参数我故意使用了引用类型Chinese& ch,相应的绑定声明也需要改为引用方式(auto& [name,bCPC])。这里要说明的是:绑定声明取决于get方法的声明,不一致会编译错误。

这块内容在语言规范9.6节第4小点有描述。

欢迎关注我的公众号。转载请标明出处。


http://www.niftyadmin.cn/n/1027757.html

相关文章

一天一点学linux

随着Linux应用的扩展许多朋友开始接触Linux&#xff0c;根据学习Windwos的经验往往有一些茫然的感觉&#xff1a;不知从何处开始学起。就像是战争&#xff0c;你有勇气战胜一切敌人&#xff0c;但你的眼前一片 漆黑&#xff0c;你知道前面有很多敌人&#xff0c;但就不知道具体…

C++核心准则T.60:最小化模板对上下文的依赖

T.60: Minimize a templates context dependencies T.60:最小化模板对上下文的依赖 Reason&#xff08;原因&#xff09; Eases understanding. Minimizes errors from unexpected dependencies. Eases tool creation. 让理解更容易。尽量减少由于意外的依赖引发的错误。方便…

【现代C++】简洁的as_cast函数模板

&#xff08;点击上方公众号&#xff0c;可快速关注&#xff09;前言C17标准库加入了as_const函数模板&#xff0c;它可以将给定的左值引用转为常量左值引用&#xff0c;用法也很简单。下面的例子中&#xff0c;假设myValue是MyType类型的值&#xff1a;// C17 const MyType&am…

C++核心准则T.61:不要过度参数化成员(SCARY)

T.61: Do not over-parameterize members (SCARY) T.61:不要过度参数化成员&#xff08;SCARY&#xff09; Reason&#xff08;原因&#xff09; A member that does not depend on a template parameter cannot be used except for a specific template argument. This limi…

【现代C++】新的字符串格式化方法

&#xff08;点击上方公众号&#xff0c;可快速关注&#xff09;本篇文章主要介绍现代C字符串格式化的方法。在此之前&#xff0c;回顾了一些老的字符串格式化的方法&#xff0c;并分析各自的优劣。在最后给出了一种提供给老编译器的折中方案&#xff0c;因为新的格式化方法需要…

C++核心准则T.62:将非依赖类模板成员放入非模板基类中

T.62: Place non-dependent class template members in a non-templated base class T.62&#xff1a;将非依赖类模板成员放入非模板基类中 Reason&#xff08;原因&#xff09; Allow the base class members to be used without specifying template arguments and without…

【C++】std::endl只是换行?

&#xff08;点击上方公众号&#xff0c;可快速关注&#xff09;前言一直用std::endl输出换行符&#xff0c;理所当然认为下面的代码等价&#xff1a;std::cout << "Hello Wolrd" << std::endl; std::cout << "Hello Wolrd" << \n…

【C++】再谈 STL reserve的坑

&#xff08;点击上方公众号&#xff0c;可快速关注&#xff09;前言之前写过一篇文章《STL reserve函数使用误区》&#xff0c;主要内容是说明一些标准模板类&#xff0c;比如&#xff0c;std::vector、std::string等&#xff0c;提供的reserve成员函数改变的是capacity&#…