如何声明接受转发引用并返回引用或副本的函数模板
How to declare a function template that accepts forwarding reference and returns either a reference or a copy
我正在尝试声明一个函数模板,该模板在传递左值时应接受并返回非常量引用,但在传递右值时返回与 RVO 兼容的本地副本:
<template ?>
? StringReplace(? str, ? from, ? to);
我希望模板生成以下签名:
对于非常量左值
std::string& StringReplace(std::string& str, const std::string& from, const std::string& to);
对于常量左值
std::string StringReplace(const std::string& str, const std::string& from, const std::string& to);
对于非常量右值
std::string StringReplace(std::string&&, const std::string&, const std::string&)
对于常量右值
std::string StringReplace(const std::string&&, const std::string&, const std::string&)
对于字符串文本
std::string StringReplace(std::string&&, const std::string&, const std::string&)
是否可以使用单个模板指定一个? 也许标准库中有一个函数或方法通过使用我应该用作参考的一个或多个模板来实现相同的结果?
查看我最终得到的版本的答案。
我相信这个基本思想符合你的要求:
template<typename Str>
Str foo(Str&& str)
{
return std::forward<Str>(str);
}
如果参数是非常量左值,则Str
被扣除为S&
,并且前向解析为S&
。
如果参数是右值,则Str
被推导出为S
,并且返回值是从参数复制/移动构造的。
如果您显式给出模板参数,则转发引用推导将被禁止,并且您必须确保给出S&
函数参数是否是S&
可以直接绑定到的左值;否则S
。
通过引用传递的函数参数永远不会有 RVO; 例如,假设调用上下文是std::string s = StringReplace( std::string("foo"), x, Y);
的,编译器此时无法知道使用与临时字符串相同的内存空间进行s
。 你能做的最好的事情就是移动构造返回值。
注意:您的原始代码尝试推断所有 3 个参数的Str
,这会导致推导冲突。您应该推断转发引用,对于其他两个,请使用非推断上下文或不同的模板参数。 例如:
template<typename Str, typename T>
Str StringReplace(Str&& str, T const& from, T const& to)
或使用 super's 答案中所示的CRef
(如果参数出现在::
的左侧,则禁用演绎)。
通过对参数和返回值进行一些积极的建模,这似乎几乎可以完成您指定的操作。
需要用<std::string&>
指定情况 2,否则转发引用将不起作用。
#include <iostream>
#include <type_traits>
template <typename T>
using CRef = typename std::remove_reference<T>::type const&;
template<typename Str>
Str StringReplace(Str&& str, CRef<Str> from, CRef<Str> to)
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
if (std::is_same<std::string&&, decltype(str)>::value)
std::cout << "rvalue-refnn";
else if (std::is_same<std::string&, decltype(str)>::value)
std::cout << "lvalue-refnn";
return std::forward<Str>(str);
}
int main() {
std::string s1;
StringReplace(s1, "", "");
// Forwarding reference will deduce Str to std::string& when passing an lvalue
StringReplace<std::string&>(s1, "", "");
StringReplace(std::move(s1), "", "");
StringReplace<std::string>(std::move(s1), "", "");
StringReplace<std::string>("", "", "");
const std::string& test = s1;
StringReplace(test, "", "");
}
如何处理传入const &
仍然存在问号。如您所见,如果您运行它,它还将返回现在的const &
。
现场示例
根据评论和答案,我最终得到了两个模板:
// true only if T is const
template<typename T>
using StringReplaceIsConst = std::conditional_t<std::is_const<std::remove_reference_t<T>>::value, std::true_type, std::false_type>;
// lvalue, lvalue reference, rvalue, rvalue reference, string literal
template<typename Str, typename = std::enable_if_t<!StringReplaceIsConst<Str>::value>>
Str StringReplace(
Str&& str,
const std::remove_reference_t<Str>& from,
const std::remove_reference_t<Str>& to
) {
return std::forward<Str>(str);
}
// const lvalue, const lvalue reference, const rvalue, const rvalue reference
template<typename Str, typename = std::enable_if_t<StringReplaceIsConst<Str>::value>>
std::decay_t<Str> StringReplace(
Str&& str,
const std::remove_reference_t<Str>& from,
const std::remove_reference_t<Str>& to
) {
std::decay_t<Str> mutableStr{std::forward<Str>(str)};
StringReplace(mutableStr, from, to);
return mutableStr;
}
现场示例
虽然上面的版本有效,但我觉得它不切实际。用户实际上对修改是就地还是在副本中完成感兴趣:
- 更简单的调试:用户可以添加日志记录来验证调用的版本
- 静态分析:用户可以对定义进行注释,使编译器自动发出警告
牢记以下几点:
// true only if T is a non-const lvalue reference
template<typename T>
using StringReplaceIsInPlace = std::conditional_t<std::is_lvalue_reference<T>::value && !std::is_const<std::remove_reference_t<T>>::value, std::true_type, std::false_type>;
// lvalue, lvalue reference, rvalue reference,
template<typename Str, typename = std::enable_if_t<StringReplaceIsInPlace<Str>::value>>
Str StringReplace(
Str&& str,
const std::remove_reference_t<Str>& from,
const std::remove_reference_t<Str>& to
) {
return std::forward<Str>(str); // forward might be redundant, as Str is always an lvalue reference.
}
// const lvalue, const lvalue reference, rvalue, const rvalue, const rvalue reference, string literal
// std::decay ensures that return is by-value and compiler can use RVO
template<typename Str, typename = std::enable_if_t<!StringReplaceIsInPlace<Str>::value>>
std::decay_t<Str> StringReplace(
Str&& str,
const std::remove_reference_t<Str>& from,
const std::remove_reference_t<Str>& to
) {
std::decay_t<Str> mutableStr{std::forward<Str>(str)}; // move construct for non-const rvalues, otherwise copy-construct
StringReplace(mutableStr, from, to);
return mutableStr; // RVO-compatible
}
第二个声明可以用[[nodiscard]]
(C++17)、[[gnu::warn_unused_result]]
(clang 和 gcc)或_Check_return_
(msvs)注释。
现场示例
- 如何通过引用返回对象
- 函数如何使用引用返回所需的数字?
- 如何防止引用返回的私有结构的突变
- 如何在不使用临时变量的情况下取消引用返回指针的函数的返回值?
- 通过引用返回的变量的范围
- 对于具有引用返回类型的搜索算法,默认返回值应该是什么?
- 运算符重载C++类中的引用返回
- C++对象引用返回不同的值
- 解释通过从函数引用返回数组的语法
- 具有引用返回类型的重写方法上的协变返回类型无效
- 为什么在通过引用返回运算符分配时取消引用'this'指针?
- 为什么我在函数中使用引用并通过引用返回它仍然有效?
- 直接在 C++ 中将值分配给引用返回类型
- C++当您取消引用指向类对象的指针,然后将其作为引用返回时,是否可以对此引用调用方法
- 可以通过常量引用返回默认参数的值吗?
- 按值与右值引用返回
- 非常量引用返回函数在常量值返回函数上用作 r 值
- 当我使用按引用返回时,我不知道这些代码之间的区别
- 为什么通过引用返回向量比通过移动返回要快得多?
- 通过引用返回包含对象的向量