首页 > 其他分享 >返回Rich return value结果思考

返回Rich return value结果思考

时间:2024-05-11 18:53:15浏览次数:14  
标签:std return 右值 value 引用 Rich 类型 模板

本文是在写过的代码中进行回顾,有理解不对的地方,望请指正!

在库(Library)或框架(Framework)设计中,"Rich return value" 是指返回值的丰富性,意味着函数返回的不仅仅是一个简单的值,而是一个包含了额外信息的复合类型。这样的设计可以提供更多的上下文信息,方便调用者理解和处理函数的执行结果。

这里需要使用Qt5::Core、Qt5::Network来实现基恩士KV8000基于NanoSerialOverTcp的读写,设计的思路参考、借鉴了C#的工业通讯框架HSLCommunication。

#pragma once

#include <QObject>
#include <QString>
#include <tuple>

/// @brief 
/// 返回结果
/// IsSuccess 具有是成功的判断
/// Message 返回的详细消息
/// ErrorCode 错误代码
/// Content 返回的内容
/// @tparam ...T 
/// 可变模板参数(variadic template parameter)
/// ...是一个包扩展(pack expansion)操作符,用于表示一个或多个模板参数
template <typename... T>
class QICResult
{
public:
	QICResult() : IsSuccess(false), Message(""), ErrorCode(0) {}
	/// @brief 关注Content
	/// @param isSuccess 
	/// @param message 
	/// @param errorCode 
	/// @param ...content 
	QICResult(bool isSuccess, const QString& message, int errorCode, T... content)
		: IsSuccess(isSuccess), Message(message), ErrorCode(errorCode), Content(std::make_tuple(content...))
	{
	}
private:
	/// @brief 不关注Content
	/// @param message 
	/// @param errorCode 
	QICResult(const QString& message, int errorCode)
		: IsSuccess(false), Message(message), ErrorCode(errorCode)
	{
	}

public:
	QString ToMessageString() const
	{
		return QString("IsSuccess: %1, Message: %2, ErrorCode: %3")
			.arg(IsSuccess)
			.arg(Message)
			.arg(ErrorCode);
	}

	template <typename... U>
	void CopyErrorFromOther(const QICResult<U...>& other)
	{
		this->IsSuccess = other.IsSuccess;
		this->Message = other.Message;
		this->ErrorCode = other.ErrorCode;
	}

	static QICResult CreateSuccessResult(T... content)
	{
		return QICResult(true, "Success", 0, content...);
	}

	static QICResult CreateFailedResult(const QString& message)
	{
		auto result = QICResult(message, -1);
		return result;
	}

	/// @brief 
	/// ...U是函数模板的参数,而T是类模板的参数。在这个函数中,T和U可能是不同的类型
	/// 因此,你需要显式地指定你想要构造哪种QICResult类型,这就是为什么你需要用QICResult<T ...>
	/// 如果你不指定T,编译器将无法确定你是想使用函数模板参数U还是类模板参数T,这将导致编译错误或不符合预期的行为
	/// @tparam ...U 
	/// @param other 
	/// @return 
	template <typename ... U>
	static auto CreateFailedResult(const QICResult<U ...>& other)
	{
		auto result = QICResult(other.Message, other.ErrorCode);
		return result;
	}

	/// @brief 通过索引获取Content中的元素
	/// @tparam U Content的元组索引值,从0开始
	/// @return 
	template <std::size_t Index>
	auto GetContent() const
	{
		static_assert(Index<std::tuple_size_v<decltype(Content)>, "Index out of bounds");
		return std::get<Index>(Content);
	}

	/// @brief 
	/// 对Content指定的索引设置值,检查类型
	/// std::is_same_v 用于比较 std::tuple_element_t<Index, std::tuple<T...>>(元组在指定索引位置的类型)与 
	/// std::decay_t<U>(传入值的类型,去掉顶层的引用和cv限定符)是否相同,如果它们不相同,static_assert 会失败,产生一个编译错误
	/// std::decay_t 是用来移除传递过来的类型的顶层 const、volatile 和引用修饰符
	/// 这通常是当你想比较两个可能是不完全相同但基础类型相同的类型时有用的。如果你想保持这些修饰符,那么你可以不使用 std::decay_t
	/// @tparam U 对应索引类型U的对象
	/// @tparam Index 索引
	/// @param value 类型为U的对象值
	template <std::size_t Index, typename U>
	void SetContent(U&& value)
	{
		// 编译时检查类型匹配
		static_assert(std::is_same_v<std::tuple_element_t<Index, std::tuple<T...>>, std::decay_t<U>>,
			"Type mismatch: The provided type does not match the tuple element type at the specified index.");
		std::get<Index>(Content) = std::forward<U>(value);
	}

public:
	bool IsSuccess;
	QString Message;
	int ErrorCode;
	std::tuple<T...> Content;
};

分析

当初编写时,没有过多的思考,一边查资料和自己有局限的理解在需要赶进度的情况下完成,有些地方没有过多的思考,这里将自己理解的在这里总结一下。

1. 可变模板参数(variadic template parameter)/模板参数包(template parameter pack)

template <typename... T> class QICResult 允许你在创建对象时传递不同的类型。当你使用类模板 QICResult 时,你可以为 T 提供任意数量和任意类型的模板参数。

例如,你可以这样创建一个 QICResult 对象:QICResult<int, std::string, float>。在这个例子中,T 被实例化为包含 int、std::string 和 float 三种不同类型的模板参数。

这种能够接受不同类型的类模板在实际开发中非常有用,特别是在需要处理不同类型数据的情况下。通过类模板,你可以实现通用的数据结构或算法,以适应不同类型的数据处理需求。

QICResult<int, QString> result = QICResult<int, QString>::CreateSuccessResult(42, "Hello");
	qDebug() << result.ToMessageString();
	qDebug() << "Content1: " << result.GetContent<0>(); // 输出 42
	qDebug() << "Content2: " << result.GetContent<1>(); // 输出 "Hello"

2. 完美转发

template <std::size_t Index, typename U>
	void SetContent(U&& value)
	{
		// 编译时检查类型匹配
		static_assert(std::is_same_v<std::tuple_element_t<Index, std::tuple<T...>>, std::decay_t<U>>,
			"Type mismatch: The provided type does not match the tuple element type at the specified index.");
		std::get<Index>(Content) = std::forward<U>(value);
	}

通过上面的代码来做写一些分析,如果修改为std::get<Index>(Content) = value会是什么样子呢?

使用 std::forward<U>(value) 和直接将 value 传递给 std::get<Index>(Content) 之间的区别在于参数的传递方式。

  1. 使用 std::forward<U>(value):
    std::get<Index>(Content) = std::forward<U>(value);
    在这种情况下,value 会根据其类型的右值或左值特性进行完美转发。如果 U 是左值引用类型,那么 value 会被传递为左值引用;如果 U 是右值引用类型,那么 value 会被传递为右值引用。这样做的好处是可以保留传递给 SetContent 方法的参数的引用类型特性,并且可以将右值传递给 std::get<Index>(Content),从而避免不必要的拷贝。

  2. 直接传递 value:
    std::get<Index>(Content) = value;
    在这种情况下,value 会被传递为它原本的引用类型。如果 value 是左值引用,那么 std::get<Index>(Content) 将接收到一个左值引用;如果 value 是右值引用,那么 std::get<Index>(Content) 将接收到一个右值引用。这种方式不会考虑参数的右值或左值特性,直接按原样传递参数。

3. 引用折叠

在模板函数 SetContent 中,虽然使用了右值引用 U&&,但是 value 参数实际上可以是左值也可以是右值。这是由于引用折叠(reference collapsing)的机制。引用折叠指的是,当一个模板参数的引用类型是一个引用的右值引用(如 T&&)时,该引用的左值特性和右值特性会根据参数的类型进行折叠。

具体来说,在 SetContent 中,如果传递的参数是一个左值,U 会被推导为左值引用类型;如果传递的参数是一个右值,U 会被推导为右值引用类型。这样,无论传递的参数是左值还是右值,都可以通过 std::forward<U>(value) 正确地进行转发。

因此,虽然函数签名是 template <std::size_t Index, typename U> void SetContent(U&& value),但传递的参数可以是左值也可以是右值。

引用折叠规则如下:
如果一个类型被折叠成左值引用和右值引用,结果是一个左值引用。
如果两个右值引用被折叠,结果是一个右值引用。
如果两个左值引用被折叠,结果是一个左值引用。
这个规则简单来说就是,当引用类型发生折叠时,会根据引用的特性进行合并。在模板函数中,通过引用折叠规则,可以正确地处理模板参数的右值和左值特性,从而实现完美转发。

4. 引用折叠发生的场景

引用折叠通常确实发生在模板的通用引用传递的情况下,但它也可能发生在其他情况下。除了模板函数的通用引用传递外,引用折叠还可能发生在类型别名(type aliasing)、模板类型推导(template type deduction)以及返回类型推断(return type deduction)等场景中。

模板函数的通用引用传递:当模板函数接受一个通用引用作为参数时,根据传递给它的参数类型(左值或右值),通用引用可能被实例化为左值引用或右值引用,并且可能发生引用折叠。

类型别名(type aliasing):在类型别名中使用通用引用时,也可能导致引用折叠。例如:

template<typename T>
using Ref = T&&;

int main() {
    int x = 5;
    Ref<int&> ref1 = x; // 引用折叠为 int&
    Ref<int&&> ref2 = std::move(x); // 引用折叠为 int&&
    return 0;
}

模板类型推导(template type deduction):在模板类型推导时,通用引用可能被推导为左值引用或右值引用,并且可能发生引用折叠。例如:

template<typename T>
void func(T&& arg) {
    // 根据 T 的类型进行相应的操作
}

int main() {
    int x = 5;
    func(x); // 推导为 int&
    func(std::move(x)); // 推导为 int&&
    return 0;
}

返回类型推断(return type deduction):在返回类型推断时,通用引用可能被推导为左值引用或右值引用,并且可能发生引用折叠。例如:

template<typename T>
auto&& forward(T&& arg) {
    return std::forward<T>(arg);
}

int main() {
    int x = 5;
    auto&& ref1 = forward(x); // 引用折叠为 int&
    auto&& ref2 = forward(std::move(x)); // 引用折叠为 int&
    return 0;
}

在这些情况下,通用引用的实例化和引用折叠是根据模板参数的具体类型以及传递给模板的参数类型来确定的。因此,引用折叠并不仅限于模板函数的通用引用传递,它可能发生在许多其他情况中。

标签:std,return,右值,value,引用,Rich,类型,模板
From: https://www.cnblogs.com/linxmouse/p/18187026

相关文章

  • mongodb keysExamined ,mongodb nreturned
    keysExamined是MongoDB查询执行阶段的一个指标,用于表示在索引扫描过程中检查的界内和界外键的总数。这个值可以帮助我们了解查询是否有效地利用了索引。如果keysExamined的值大于0,这意味着查询正在使用索引。如果keysExamined的值非常高,但返回的文档数量(nreturned)却很低,这可能表明......
  • dotnet 9 WPF 支持 Style 的 Setter 填充内容时可忽略 Value 标签
    本文记录WPF在dotnet9的一项XAML编写语法改进点,此改进点用于解决编写Style的Setter进行给Value赋值时,不能将Value当成默认内容,需要多写Value标签的问题。通过此改进点可减少两行XAML代码在原先的WPF版本里面,对Style的Setter填充复杂的对象内容时,大概的......
  • 解决Vue3项目警告:xxxis-declared-but-its-value-is-never-read
    刚刚在Vue3项目引入的一个组件Person下有红线,系统给出了警告,这是因为TypeScript会检查代码中未使用的变量,我定义了'Person'的变量,但是后续代码没有使用到它,从而导致Vetur(Vue的语法检查工具)给出了这个警告。解决方法:方法一:你可以删除或者在代码中使用'Person'变量或类型,以......
  • [20240426]sql_id 转换hash_value.txt
    [20240426]sql_id转换hash_value.txt--//以前写的脚本,转换sql_idtohash_value.遇到问题:$cats2p.sh#!/bin/bash#convertsql_idtohash_valueodebug=${ODEBUG:-0}sql_id="$*"v1=$(echo$sql_id|tr$(echo{0..9}{a..z}|tr-d'eilo')$(echo{0..9}{a.......
  • c# Dictionary<TKey,TValue>.TryAdd
    原文链接:https://learn.microsoft.com/zh-cn/dotnet/fundamentals/code-analysis/quality-rules/ca1864Dictionary<TKey,TValue>.ContainsKey(TKey) 和 Dictionary<TKey,TValue>.Add 都执行查找操作,这是冗余设置。如果字典中已存在键,Dictionary<TKey,TValue>.Add 也会引发异......
  • Rich:终端打印富文本
    Rich——一个让程序更高级的Python库在这个多彩缤纷的数字时代,命令行界面似乎太过单调乏味。想象一下,如果你的终端输出能够像现代网页一样丰富多彩,是不是会带给你更愉快的开发体验?这时,一个强大的库——Rich——悄然走进了你的视野。它让颜色和样式的添加变得异常简单,不仅......
  • Richard 林旅强:说说社区的故事和对 RTE 社区的畅想
    各位RTE开发者社区的小伙伴们,大家好: 我是Richard林旅强,今年起开始担任我们RTE社区联合主理人,很荣幸能在这里跟杜金房老师和陈靖老师一起做点事情,为社区的大家服务:) 今天想跟各位分享,我参与社区的几个故事,也希望对各位RTE的小伙伴能有启发和收获。第一个故事:从玩......
  • Js中valueOf和toString区别和使用
    对于number、string、Boolean、object、symbol数据类型调用valueOf方法,得到的都是数据本身(null、undefined两种类型上的原型链上没有valueOf方法)点击查看代码vara=1;varaa=a.valueOf();console.log(aa==a);//truevarb='a';......
  • 重写DRF的to_representation和to_internal_value方法有什么用途?
    DRF所有序列化器类都继承了BaseSerializer类,通过重写该类的to_representation()和to_internal_value()方法可以改变序列化和反序列化的行为,比如给序列化后的数据添加额外的数据,或者对客户端API请求携带的数据进行反序列化处理以及用来自定义序列化器字段。to_representation(......
  • 修改序列last_value 字段
    在PostgreSQL中,你不能直接更新序列(如seq_sys_config)的last_value字段,因为序列是一个特殊的系统对象,不允许你像普通表那样直接修改它的列。last_value实际上是序列的一个伪列,表示最后返回的值,但它不是一个可以直接设置的列。如果你想要修改序列的当前值或者重置它,你应该使用......