如何声明接受转发引用的函数模板并返回引用或副本

时间:2018-03-17 00:50:21

标签: c++ c++14 perfect-forwarding

我正在尝试声明一个函数模板,它应该在传递左值时接受并返回非const引用,但在传递右值时返回与RVO兼容的本地副本:

<template ?>
? StringReplace(? str, ? from, ? to);

我希望模板生成以下签名:

  1. 表示非常数左值

    std::string& StringReplace(std::string& str, const std::string& from, const std::string& to);
    
  2. for const lvalues

    std::string StringReplace(const std::string& str, const std::string& from, const std::string& to);
    
  3. 表示非const rvalues

    std::string StringReplace(std::string&&, const std::string&, const std::string&)
    
  4. 用于const rvalues

    std::string StringReplace(const std::string&&, const std::string&, const std::string&)
    
  5. 用于字符串文字

    std::string StringReplace(std::string&&, const std::string&, const std::string&)
    
  6. 是否可以使用单个模板指定一个? 也许标准库中有一个函数或方法通过使用一个或多个我应该用作参考的模板来实现相同的结果?

    请参阅my answer了解我最终的版本。

3 个答案:

答案 0 :(得分:3)

我相信这个基本想法符合您的要求:

template<typename Str>
Str foo(Str&& str)
{
    return std::forward<Str>(str);
}

如果参数是非常数左值,那么Str会被扣除到S&,前转也会解析为S&

如果参数是rvalue,则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)

或使用超级答案中显示的CRef(如果参数显示在::的左侧,则会扣除扣除)。

答案 1 :(得分:2)

通过对参数和返回值进行一些积极的模拟,这似乎几乎与您指定的一致。

案例2需要使用<std::string&>指定,否则转发参考将无效。

#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-ref\n\n";
    else if (std::is_same<std::string&, decltype(str)>::value)
        std::cout << "lvalue-ref\n\n";

    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 &

Live example

答案 2 :(得分:1)

根据评论和答案,我最终得到了两个模板:

// 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;
}

Live example

虽然上面的版本有效但我发现它不切实际。用户实际上对修改是到位还是在副本中感兴趣:

  • 更简单的调试:用户可以添加日志以验证调用哪个版本
  • 静态分析:用户可以注释定义以使编译器自动发出警告

牢记这些要点:

// 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)进行注释。

Live example