【问题标题】:Deducing a const l-value reference from a non-const l-value reference in C++ template从 C++ 模板中的非 const 左值引用推导出 const 左值引用
【发布时间】:2019-03-06 23:32:54
【问题描述】:

假设你有以下一对函数:

void f(const int&) { 
    // Do something, making a copy of the argument.
}

void f(int&&) { 
    // Do the same thing, but moving the argument.
}

它们是相当多余的——函数之间的唯一区别是它们是复制还是移动它们的参数。当然,我们可以通过将其重写为单个模板函数来做得更好:

template<typename T> 
void g(T&&) { 
    // Do something, possibly using std::forward to copy or move the argument.
}

这行得通,并且是实践中常用的成语。但是模板可能被实例化为三个函数,从我们上面的两个函数开始。我们可以使用以下代码来验证这种情况:

#include <iostream>

template<typename T> constexpr char *type = nullptr;
template<> constexpr const char *type<int&> = "int&";
template<> constexpr const char *type<const int&> = "const int&";
template<> constexpr const char *type<int> = "int";

template<typename T> 
void g(T&&) { 
    std::cout << reinterpret_cast<void*>(&g<T>)
              << " = &g<" << type<T> << ">" << std::endl;
}

int main() {
    int i = 0;
    const int& cr = 0;

    g(i);
    g(cr);
    g(0);

    return 0;
}

/*
Prints:

0x100f45080 = &g<int&>
0x100f45100 = &g<const int&>
0x100f45180 = &g<int>
*/

这为T = int&amp; 的情况添加了第三个函数,这是我们在上面使用非模板函数f 时没有的。在这种情况下,我们实际上并不需要这个函数的非 const l 值引用版本——假设 f 足以满足我们最初的需求——这会增加我们代码的大小,尤其是在我们有很多模板函数的情况下写成这样互相呼唤。

有没有办法编写上面的函数g,以便在我们的示例代码中调用g(i) 时,编译器会自动推导出T = const int&amp;?即,我们不必手动编写 g&lt;const int&amp;&gt;(i) 仍能获得所需行为的方式。

【问题讨论】:

  • 函数有可能被完全内联,移除了额外的函数。
  • @Jarod42 同意此处示例中的函数大小。尽管取决于f 和g 的大小以及它们自己调用的更多函数,但它开始变得更加模棱两可。以前见过这种情况没有被内联。
  • 如果您担心相同的功能,链接器通常可以选择合并它们。 (如黄金/lld的--icf)
  • 实际上每个值类型有 8 种可能的实例化:cv-qualifiers 使它成为 2(for constness)x 2(for volatileness),r/l-valueness 将它乘以另一个 2。

标签: c++ c++11 templates perfect-forwarding template-argument-deduction


【解决方案1】:

说“前向引用”(“通用引用”)比专用重载更好是一种主观的观点。在很多情况下确实如此,但如果你想完全控制,他们不会做所有的工作。

您可以通过添加明确确保用户不传递非常量左值引用

    static_assert(!std::is_lvalue_reference<T>::value || std::is_const<typename std::remove_reference<T>::type>::value, "only call g with const argument");

在 g 中,但这并不是在所有情况下都是一个很好的解决方案。

或者您执行对 vector::push_back(...) 所做的操作并提供显式重载——但这是您的起点,请参阅https://en.cppreference.com/w/cpp/container/vector/push_back。

“正确”答案取决于您的要求。

编辑: @Sjoerd 的建议看起来像:

template <typename T>
class aBitComplicated {
public:
 void func(T&& v) { internal_func(std::forward<T>(v)); }
 void func(const T& v) { internal_func(v); }
private:
 template <typename U>
 void internal_func(U&& v) { /* your universal code*/ }
};

还有一个更复杂/复杂的版本,但这应该是实现您所要求的最简单的版本。

【讨论】:

  • 另一个选项是调用私有模板的两个重载。然后,您可以完全控制生成哪些模板实例化,但仍然可以从拥有模板中获益。
  • @Sjoerd 那是 max66 的答案,但由于某种原因他们删除了它。
  • @Sjoerd 那会是什么样子?
  • 我会在我的回答中添加一个这个建议的例子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-06
  • 2018-02-20
  • 1970-01-01
  • 2017-04-13
相关资源
最近更新 更多