【问题标题】:Overload a method in a way that generates a compiler error when called with a temporary以在使用临时调用时生成编译器错误的方式重载方法
【发布时间】:2018-10-25 18:15:10
【问题描述】:

也许这段代码最能说明我的意图:

#include <array>

template <size_t N>
void f(std::array<char, N> arr)
{
}

template <size_t N>
void f(std::array<char, N>&& arr)
{
    static_assert(false, "This function may not be called with a temporary.");
}

f() 应该为左值而不是右值编译。此代码适用于 MSVC,但 GCC 会在 static_assert 上跳闸,即使从未调用过此重载。

所以我的问题有两个:如何用现代 C++ 正确表达我的意图,以及为什么编译器在从未实例化的“死”模板重载中评估 static_assert

在线试用:https://godbolt.org/z/yJJn7_

【问题讨论】:

  • 这不会导致警告而是硬错误。
  • 你想让arr在函数中是可修改的吗?
  • FWIW,禁止右值并没有真正起作用。 Titus Winters 给出了a talk at C++Now 并在 CppCon(现代 C++ 设计)的一次演讲中提到了这一点。简而言之,右值不是生命周期的指标。禁用右值重载将导致程序员简单地将临时变量放入局部变量中,这不会真正影响生命周期要求
  • @NathanOliver:不,这不是必需的。你有什么想法?
  • @Justin:在我的情况下,除了局部变量完全没问题,但使用临时调用 f 会出现段错误或产生垃圾。

标签: c++ templates c++14 c++17 rvalue


【解决方案1】:

一种选择是删除static_assert 并将函数标记为已删除。然后如果你用右值调用它,你会得到一个错误,说你正在尝试使用一个已删除的函数

template <size_t N>
void f(const std::array<char, N>& arr)
{

}

template <size_t N>
void f(const std::array<char, N>&& arr) = delete; // used const here just in case we get a const prvalue

int main()
{
    std::array<char, 3> foo{};
    f(foo);
    //f(std::array<char, 3>{}); // error
    return 0;
}

【讨论】:

  • 您应该使用f(X const&amp;&amp;) = delete 而不是f(X&amp;&amp;) = delete。后者还将捕获 const rvalues - 按原样会更喜欢您的其他重载 - 并且适用于非 const rvalues。
  • @Barry:const rvalue 在呼叫站点上的外观如何?要么我不明白这个概念,要么我不明白如何获得一个。
  • @VioletGiraffe 与其他任何右值的外观相同,但它是 const。如果x 是常量,std::move(x); 是常量右值。
【解决方案2】:

足够简单。

template <size_t N>
void f(const std::array<char, N>&& arr) = delete;

【讨论】:

  • @NathanOliver:它使用 MSVC 和 GCC 8.1 在 Godbolt 上编译。
  • 好样的,谢谢!我一直忘记你可以delete 各种功能,而不仅仅是特殊的自动生成的类成员。
  • @VioletGiraffe 是的,它不会编译 f(std::array&lt;char, 3&gt;{});,这是你不想要的,所以没关系。只是错误消息不是最好的:godbolt.org/z/VmXY17。评论已删除
  • @NathanOliver 为我编译?
  • @VioletGiraffe 是的。您不能省略左值(当然可以,但这是针对返回的函数局部变量)
【解决方案3】:

可以只使用一个引用非 const 对象的函数:

template<size_t N> void f(std::array<char, N>& arr);

不再需要重载。

此规则由语言规范强制执行。 然而Visual C++ 编译器有一个扩展,允许将右值传递给这样的函数。

【讨论】:

  • 我相信这会产生编译器警告,但不会产生错误?当然,有一种特定于编译器的方法可以将特定警告提升为错误,并且此警告在任何项目中都是非常可靠的候选者,但我更喜欢可移植的、语言强制的方式。
  • @VioletGiraffe 如果您使用的是 Visual Studio 2017 编译器,请将 /permissive- 传递给它。在以前的版本中,我相信有一个禁用编译器扩展的标志
  • @VioletGiraffe 旧 MSVC 标志是 /Za 我相信:docs.microsoft.com/en-us/cpp/build/reference/…
  • @Justin:谢谢,我不知道/permissive-,这是一个很棒的功能!
【解决方案4】:

除了其他答案,我想指出标准库中有一个完全符合 OP 想要的示例 - std::addressof

template<class T>
constexpr T* addressof(T&) noexcept;

template<class T>
const T* addressof(const T&&) = delete;

【讨论】:

  • 谢谢,一个有趣的类比。但只要我想投射引用(我会这样做),它就不再起作用了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-16
  • 1970-01-01
  • 2011-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
相关资源
最近更新 更多