在C++中,如何编写一个二元函数模板,使其参数能够以常量引用和右值引用这两种形式的四种组合来传递?

编程语言 2026-07-11

为了说明我的问题,我在这里用一个简化的例子:
给定一个结构体,其中包含一个向量,我需要对它做一些操作(例如,与另一个向量拼接)。
考虑到它的大小、灵活性和性能,我写了一个接受const引用的函数,以及一个接受右值引用并使用移动语义来提升性能的版本。

using namespace std;

struct StringList
{
    vector<string> texts;
};

void bar(const StringList&)
{
    std::cout << "bar(const StringList&)\n";
    // do something
}

void bar(StringList&&)
{
    std::cout << "bar(StringList&&)\n";
    // do something using std::move or std::ranges::move
}

另外,我需要编写二元函数,根据参数类型调用前面两个函数,如下所示:

void foo(const StringList& a, const StringList& b)
{
    bar(a);
    bar(b);
}

void foo(const StringList& a, StringList&& b)
{
    bar(a);
    bar(std::move(b));
}

void foo(StringList&& a, const StringList& b)
{
    bar(std::move(a));
    bar(b);
}

void foo(StringList&& a, StringList&& b)
{
    bar(std::move(a));
    bar(std::move(b));
}

现在,我可以这样调用它们:

StringList l1;
StringList l2;
foo(l1, l2);
foo(l1, StringList{{"A", "B"}});
foo(StringList{{"A", "B"}}, l2);
foo(StringList{{"A", "B"}}, StringList{{"C", "D"}});

然而,由于C++支持函数模板,以及其他大量特性,如完美转发、if constexpr、类型特征、概念和requires,我猜可以只写一个函数模板,而不必枚举所有可能的参数类型排列。
不过,在一些失败的尝试之后,我发现函数模板中推导出的类型往往不是我所预期的。例如,用 StringList{} 调用下面的函数模板时,最终会调用 foo<StringList, StringList>bar<const StringList&> 而不是 bar(StringList&&)

template <typename TLeft, typename TRight>
void foo(TLeft a, TRight b)
{
    if constexpr (std::is_same_v<TLeft, StringList&&>)
    {
        bar(std::move(a));
    }
    else
    {
        bar(a);
    }

    if constexpr (std::is_same_v<TRight, StringList&&>)
    {
        bar(std::move(b));
    }
    else
    {
        bar(b);
    }
}

我不认为把所有参数的可能排列逐一列出并为每一种排列编写函数是最聪明的方式。在C 里编写四个函数并不算大事,但想象你需要支持不止两种类型,而且还是三元的。有人能给我一些提示吗?

解决方案

你走在正确的路线上,但forward­ing reference(转发引用)才是正确的思路

template <typename T = StringList, typename U = StringList>
void foo(T&& a, U&& b)
    requires std::is_same_v<std::remove_cvref_t<T>, StringList> &&
             std::is_same_v<std::remove_cvref_t<U>, StringList>
{
    bar(std::forward<T>(a));
    bar(std::forward<U>(b));
}

LIVE

两个参数都是“转发引用”,可以匹配右值或左值。使用 std::forward,你可以保证原始的值类别被传递给 bar

默认模板参数允许像这样的调用:foo(l,{{"A", "B"}}); 其中 l 是一个 StringList 对象(多亏了Jarod42)。

requires子句确保只能使用 StringList

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章