【问题标题】:Implement template type constructors only if underlying type has those constructors仅当底层类型具有这些构造函数时才实现模板类型构造函数
【发布时间】:2018-01-22 20:47:22
【问题描述】:

有没有办法将类型封装在模板类中(类似于 std::optional 之类的东西),它具有所有必要的特殊构造函数和赋值运算符(即复制 ctor/assignment、移动 ctor/assignment)但仅“启用“如果基础类型具有这些功能,它们会怎样? type_traits 中的函数如 std::is_copy_constructible 看起来可能会有所帮助,但我不确定如何使用它们来实现这一目标。作为参考,我尝试实现的类型类似于std::optional,但我想使用自定义错误类型而不是简单的“无”替代值。例如

template <typename T>
class ErrorOr {
 public:
  enum class Error {
    FATAL,
    WARNING,
    NONE,
  };

  ErrorOr(T val) : val(val), error(Error::NONE) {}
  ErrorOr(Error error) : error(error) {}

  // TODO: Implement copy/move ctors/assignment operators that only
  // exist if they do for the underlying T

  T get() { val; }

 private:
  T val;
  Error error;
};

这是一个非常简单/最小的实现,没有很多必要的功能,但希望能说明我想要表达的观点。

这在 C++11 中可行吗?

【问题讨论】:

  • 是的,可选的就是这样工作的。
  • 您在 get 成员函数中缺少 return。另请注意,ErrorOr 已经假定 T 是可复制的,因为它在其 ErrorOr(T val) 构造函数中复制了一个 T 值。
  • 简单的方法是实现它们(即使T 不支持它)。当使用不受支持的方法时(并且仅在使用它时),您会收到编译错误。 SFINAE/static_assert 可能有更好的错误信息。 SFINAE 将允许在 SFINAE 上下文中使用它们(但在最坏的情况下,用户可以使用 T 来实现)。
  • 有了这个sn-p,隐式声明的特殊成员就会做正确的事。你到底想做什么?

标签: c++ c++11 templates typetraits


【解决方案1】:

在这种情况下,什么也不做。 ErrorOr&lt;T&gt; 拥有T 类型的成员将默认所有特殊成员函数来做正确的事情。如果T 不可复制,ErrorOr&lt;T&gt; 也不会。


但是,这也不是真正的可选类型,因为您总是拥有T。如果您最终转移到有条件地具有T 的实现, 一种方法是从一个空类型继承,该类型可以根据需要启用或禁用特殊成员。

简化版本是:

template <bool allowCopies>
struct copy_enabler {
    copy_enabler() = default;
    copy_enabler(copy_enabler const& ) = default;
    copy_enabler& operator=(copy_enabler const& ) = default;
};

template <>
struct copy_enabler<false> {
    copy_enabler() = default;
    copy_enabler(copy_enabler const& ) = delete;
    copy_enabler& operator=(copy_enabler const& ) = delete;
};

那么你可以简单地:

template <typename T>
class ErrorOr : private copy_enabler</* is T copyable */> { ... };

在实践中,您将希望对所有特殊成员函数执行此操作,并添加一个标记类型,以便如果您对多个不同的类模板使用此技巧,它们最终不会共享一个公共基础。

【讨论】:

    【解决方案2】:

    在下面的代码中,X 类只有在其模板参数是类类型并且是可复制构造的(这意味着它应该具有复制构造函数)时才定义复制构造函数:

    template <typename T>
    class X {
      static constexpr bool has_copy_constructor
        = std::is_class<T>::value && std::is_copy_constructible<T>::value;
      struct dummy_type { };
    public:
      X() = default;
      X(typename std::conditional<has_copy_constructor,
        const X &, const dummy_type &>::type) { std::cerr << "copy ctor\n"; }
      X(typename std::conditional<has_copy_constructor,
        const dummy_type &, const X &>::type) = delete;
    };
    
    int main() {
      X<std::string> x1;
      X<std::string> x2(x1); // OK
    
      X<int> x3;
      X<int> x4(x3); // ERROR
    }
    

    不是我喜欢这个解决方案,但它确实有效。

    【讨论】:

      【解决方案3】:

      是的,使用 std::enable_if(在较新的 c++ 版本中使用 std::enable_if_t,不确定是否在 c++11 中添加)。

      template<typename T>
      struct my_type
          : T
      {
          my_type(T && val, typename std::enable_if<std::is_move_constructible<T>::value>::type * = nullptr)
              : T(std::move(val))
          {
          }
      };
      

      【讨论】:

      • C++11 有enable_if&lt;...&gt;::type,C++14 有等价的enable_if_t&lt;...&gt;
      • 根据 OP 的需要,这将不起作用。例如,intmove constructibe,但没有 move 构造函数。
      • 如果my_type 继承自T,那么无论特殊成员是否有效,您都已经在继承。 enable_if 不会为你做任何事情。
      • @DanielLangr 好吧,更大的问题是你不能从int继承。
      • 在这里使用std::move 似乎没有意义,因为val 已经是一个右值引用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-01
      • 2017-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多