【问题标题】:C++ Conditional template constructorC++ 条件模板构造函数
【发布时间】:2021-07-24 11:53:30
【问题描述】:

我有一个类似于智能指针的HandleID 类。以下是重要的部分:

template<class T>
class HandleID
{
// Only if T is not const
friend class HandleID<const T>;

public:
    HandleID();
    HandleID(int id);
    HandleID(const HandleID<T>& other);
    HandleID& operator=(const HandleID<T>& other);

    // Only if T is const
    HandleID(const HandleID<const removed T>& other);
    HandleID& operator=(const HandleID<const removed T>& other);


private:
    T* cachedPointer;
    int id;
};

现在我希望能够从HandleID&lt;T&gt; 构造一个HandleID&lt;const T&gt;,但反过来不行。复制赋值运算符也是如此:HandleID&lt;const T&gt; = HandleID&lt;T&gt; 应该是合法的,但 HandleID&lt;T&gt; = HandleID&lt;const T&gt; 是不合法的。

现在我考虑为此添加模板专业化或其他东西,但我确信有更好的方法来做到这一点。请注意,非 const 版本必须将 const 版本添加为友元,才能访问构造函数/assignmnent 运算符中的私有成员。

【问题讨论】:

  • std::enable_if 可能有效,但您也可以简单地将模板专门用于 const 参数...

标签: c++ templates constructor sfinae enable-if


【解决方案1】:

SFINAE 方式...

为简化起见,您可以为无常量 T 添加一个 using 类型

using no_const_T = std::remove_const_t<T>;

T不是const时等于T,当Tconst时不同。

只有当Tno_const_T 不同时,SFINAE 才能启用构造函数/运算符

template <typename U = T,
          std::enable_if_t<not std::is_same_v<U, no_const_T>, int> = 0>
HandleID(const HandleID<no_const_T> & other);

template <int..., typename U = T, 
          std::enable_if_t<not std::is_same_v<U, no_const_T>, int> = 0>
HandleID& operator=(const HandleID<no_const_T>& other);

请注意,您必须检查T 是否与no_const_T 相同或不同,而不是直接使用T,而是使用T 初始化的本地(对于方法)模板参数(U)。

-- 编辑--

OP 询问

当我想将声明(您提供的那个)和实现(例如,在文件中更下方的类之外)分开时,语法是什么

这是一种谵妄。

以下是一个完整的编译(愚蠢)示例,它在类的主体之外实现了启用 SFINAE 的方法。

#include <type_traits>

template <typename T>
class HandleID
 {
   friend class HandleID<T const>;

   using no_const_T = std::remove_const_t<T>;

   public:
      HandleID () {}
      HandleID (int) {}
      HandleID (HandleID<T> const &) {}

      template <typename U = T,
                std::enable_if_t<not std::is_same_v<U, no_const_T>, int> = 0>
      HandleID (HandleID<no_const_T> const &);

      HandleID & operator= (HandleID<T> &)
       { return *this; }

      template <int..., typename U = T,
                std::enable_if_t<not std::is_same_v<U, no_const_T>, int> = 0>
      HandleID & operator= (HandleID<no_const_T> const &);
 };

template <typename T>
template <typename U,
          std::enable_if_t<not std::is_same_v<U,
             std::remove_const_t<T>>, int>>
HandleID<T>::HandleID (HandleID<std::remove_const_t<T>> const &)
 { }

template <typename T>
template <int..., typename U,
          std::enable_if_t<not std::is_same_v<U,
             std::remove_const_t<T>>, int>>
HandleID<T> & HandleID<T>::operator=
   (HandleID<std::remove_const_t<T>> const &)
 { return *this; }

int main()
 {
   HandleID<int>       id0;
   HandleID<int const> idc0;

   HandleID<int>  id1{id0};  // copy constructor: compile
   //HandleID<int>  id2{idc0}; // constructor disabled: compilatrion error

   HandleID<int const>  idc1{idc0};  // copy constructor: compile
   HandleID<int const>  idc2{id0};   // constructor enabled: compile
 }

【讨论】:

  • 这很聪明,我喜欢!对于朋友类,我想将自己添加为朋友应该不是问题吧?另外,模板实现中的语法是什么,是:template &lt;int..., typename U = T, std::enable_if_t&lt;not std::is_same_v&lt;U, HandleID&lt;T&gt;::no_const_T&gt;, int&gt; = 0&gt; HandleID&lt;T&gt;&amp; operator=(const HandleID&lt;no_const_T&gt;&amp; other); 还是我必须在它前面添加另一个模板
  • @AdrianAlbertKoch - 就其本身的友好性而言:确切地说,应该不是问题。
  • @AdrianAlbertKoch - 好的:我尝试以这种方式制作完整的示例。
  • @AdrianAlbertKoch - 运算符的int... 并不是绝对必要的。我添加它是为了防止用户启用它,什么时候应该禁用它,解释U。之前添加可变参数模板参数,防止这种可能性。这对于构造函数来说不是必需的,因为(据我所知)为模板构造函数显式的模板参数是不可能的。
  • @AdrianAlbertKoch - 找到了一种同时使用 g++ 和 clang++ 进行编译的方法。诀窍是在外部实现中使用HandleID&lt;std::remove_const_t&lt;T&gt;&gt; 而不是typename HandleID&lt;T&gt;::no_const_T&gt;。也许我会添加一个问题,问是g++还是clang++。
【解决方案2】:

在 C++20 中,它将是:

template<class T>
class HandleID
{
friend class HandleID<const T>;

public:
    HandleID();
    HandleID(int id);
    HandleID(const HandleID<T>& other);
    HandleID& operator=(const HandleID<T>& other);

    HandleID(const HandleID<std::remove_const_t<T>>& other) requires (std::is_const_v<T>);
    HandleID& operator=(const HandleID<std::remove_const_t<T>>& other) requires (std::is_const_v<T>);

private:
    T* cachedPointer;
    int id;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    • 2011-03-27
    • 2016-12-04
    • 1970-01-01
    • 2020-08-08
    • 1970-01-01
    • 2019-10-08
    相关资源
    最近更新 更多