c++ - 从 std 命名空间专门化函数模板的想法有多糟糕?

标签 c++ c++11 design-patterns std

代理对象和移动语义有问题。它们并不总是结合得很好。在我的容器类中,我有一个重载的 operator[],它在容器内使用惰性元素创建。因此,operator[] 返回一个代理对象,该对象保留对容器的现有元素的引用或潜在新元素的位置。这个想法是,无论何时以特定方式访问代理对象或将某些东西分配给它时,都会在该地方的容器中自动创建一个新元素。

问题出在返回代理对象的方式上:

Proxy operator [](std::string name);

我被迫按值返回,因为我希望调用者管理代理的生命周期。按值返回的问题在于 operator [] 调用的结果是一个 prvalue,这意味着无论何时调用函数来初始化或分配一个新值,它总是会被移动对象:

/// Element of container identified by name "foo" is moved from.
Element foo(container["foo"]);
/// Same with bar...
foo = container["bar"];

我希望我的代理可以移动分配给一个元素,以获得流畅的开发体验。但我希望它以一种通常的、预期的方式出现,就像这样:

/// Element is copied
Element foo1 = container["foo"];
/// Element is moved
Element foo2 = std::move(container["foo"]);

就我所知,它可以通过第二个代理对象 MovableProxy 来实现。根据这个策略,我想将 Proxy 配置为只能复制。然后我想将 std::move 专门化为 Proxy 类型,以便在提供 rvalue 时返回 MovableProxyProxylvalue:

namespace std
{

MovableProxy move(Proxy & foo)
{
    ...
}

MovableProxy move(Proxy && foo)
{
    ...
}

} // namespace std

那么,为我自己的类专门化模板函数 std::move 的想法有多糟糕?拐角处有哪些可怕的陷阱?

最佳答案

您的代码是重载 move 的示例, 没有专门化它,那是永远不合法的。

只要您的特化遵循 std 对相关模板的任何规范,对 std 中的大多数模板进行特化是合法的。然而,重载与专门化相同。

重载违反了将新符号插入 std 的规则.它使您的程序格式错误,无需诊断。


我可能会考虑写一个 notstd::move转发到std::move并具有额外类型的重载,或启用移动的 ADL 查找...

namespace notstd{
  namespace adl_helper{
     template<class T>
     decltype(auto) mover(T&&){
       using std::move;
       // finds both std::move and any move in T's namespace:
       return move(std::forward<T>(t));
     }
  }
  namespace adl_blocker {
    // will not be found by types in notstd via ADL:
    template<class T>
    decltype(auto) move(T&& t){
      return ::notstd::adl_helper::mover(std::forward<T>(t));
    }
  }
  // import into notstd so it can be named
  // via notstd::move:
  using namespace ::notstd::adl_blocker;
}

或者类似的东西。

现在friend move将由 notstd::move(x) 调用代替 std::move如果是更好的匹配。


事实证明,您不能将其重写为特化,因为您想要更改返回类型。 std::move定义为:

template< class T >
constexpr typename std::remove_reference<T>::type&& move( T&& t )

在 C++14 中。您的特化必须同意上述返回类型。

我们可以尝试治疗 remove_reference作为改变返回类型的特征(坏主意!),但这在标准下是非法的,因为这种特化会违反要求 std穿remove_reference .

理论上,你可以这样写:

namespace std {
  template<>
  constexpr typename std::remove_reference<Proxy>::type&& move( Proxy&& t ) {
    t.set_move_flag = true;
    return static_cast<Proxy&&>(t);
  }
  template<>
  constexpr typename std::remove_reference<Proxy>::type&& move( Proxy const&& t ) {
    return static_cast<Proxy const&&>(t);
  }
  template<>
  constexpr typename std::remove_reference<Proxy&>::type&& move( Proxy& t ) {
    t.set_move_flag = true;
    return static_cast<Proxy&&>(t);
  }
  template<>
  constexpr typename std::remove_reference<Proxy const&>::type&& move( Proxy const& t ) {
    return static_cast<Proxy const&&>(t);
  }
}

但这太可怕了,如果您尝试使用它,我什至不愿意将它张贴在这里。 (我不知道上面的代码是否包含错误,无论是否是基本错误,但这是我能想到的最接近可能工作的方法。)

关于c++ - 从 std 命名空间专门化函数模板的想法有多糟糕?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42305317/

相关文章:

c# - 替代使用 if -else 语句

c++ - 仅 header 库中的全局变量

c++ - 优化一个简单的 2D Tile 引擎(+潜在错误修复)

c++ - 为什么 UNIX 文件描述符不是由它们自己的类型实现的,尤其是在 C++ 中?

C++11 可变参数模板在类中调用函数

java - 如何识别多线程环境下的非线程安全代码?

c++ - 什么是 'linkonce' 部分

c++ - 为什么 {} 用于访问 std::hash 中的 operator()?

c++ - 使用标准算法将容器分区/批处理/分块成大小相等的 block

design-patterns - 这叫做 "Circular Reference"吗?