为可移动类型输入特征?

时间:2011-08-14 04:10:03

标签: c++ c++11 typetraits

我正在尝试编写一个模板,如果T有移动构造函数,则表现为单向;如果T没有,则尝试另一种方式。我试图寻找一种可以识别出这种情况的类型特征,但却没有这样的运气,而我为此编写自己的类型特征的尝试都失败了。

任何帮助表示感谢。

6 个答案:

答案 0 :(得分:18)

我觉得有必要指出一个微妙的区别。

虽然<type_traits>确实提供了std::is_move_constructiblestd::is_move_assignable,但这些并不能准确地检测某个类型是否具有移动构造函数(分别是移动赋值运算符)。例如,std::is_move_constructible<int>::valuetrue,并考虑以下情况:

struct copy_only {
    copy_only(copy_only const&) {} // note: not defaulted on first declaration
};
static_assert( std::is_move_constructible<copy_only>::value
             , "This won't trip" );

请注意,用户声明的复制构造函数会抑制移动构造函数的隐式声明:甚至没有隐藏的,编译器生成的copy_only(copy_only&&)

类型特征的目的是促进泛型编程,因此根据表达式(缺少概念)来指定。 std::is_move_constructible<T>::value提出的问题是:例如T t = T{};有效吗?它不是要求(假设T是这里的类类型)是否有T(T&&)(或任何其他有效形式)移动构造函数被声明。

我不知道你要做什么,我没有理由不相信std::is_move_constructible不适合你的目的。

答案 1 :(得分:9)

它被称为std::is_move_constructable。还有std::is_move_assignable。它们都在C ++ 0x <type_traits>标题中。

答案 2 :(得分:3)

经过一番讨论,并完全同意这可能完全没用,并且警告说旧版编译器可能会出错,我还是想粘贴一个我操作过的小特质课程,我相信会给你仅当类具有移动构造函数时才true

#include <type_traits>

template <typename T, bool P> struct is_movecopy_helper;

template <typename T>
struct is_movecopy_helper<T, false>
{
  typedef T type;
};

template <typename T>
struct is_movecopy_helper<T, true>
{
  template <typename U>
  struct Dummy : public U
  {
    Dummy(const Dummy&) = delete;
    Dummy(Dummy&&) = default;
  };
  typedef Dummy<T> type;
};

template <class T>
struct has_move_constructor
 : std::integral_constant<bool, std::is_class<T>::value &&
   std::is_move_constructible<typename is_movecopy_helper<T, std::is_class<T>::value>::type>::value> { };

用法:has_move_constructor<T>::value

请注意,编译器特征std::is_move_constructible实际上并未附带GCC 4.6.1,必须单独提供,请参阅我的complete code

答案 3 :(得分:2)

更新3: :我已添加another answer,因此请忽略此操作。我很想删除它,因为它不再适用于我的新编译器。但是我已经有了一些回复,所以我想我不应该删除它。此外,这个特定的答案确实适用于一些较旧的编译器,因此它可能对某些人有用。

这将测试是否存在T(T&&)形式的构造函数。 适用于clang-3.3和g ++ - 4.6.3。但this test on ideone表明他们的编译器(g ++ - ???)混淆了复制和移动构造函数。

更新2:2015年1月 。这不适用于较新的g ++(4.8.2)和clang(3.5.0)。所以我想如果没有先检查你的特定版本是否支持我在这里使用过的技巧,我的答案就无济于事。也许我的伎俩不符合标准,因此已从g ++和clang ++中删除。在我下面的回答中,我说“派生类只有一个隐式移动构造函数,如果它的所有基础都有移动构造函数” - 也许这不是真的或太简单了?

struct move_not_copy { move_not_copy(move_not_copy &&); };

template<typename T>
struct has_move_constructor {
        struct helper : public move_not_copy,  public T {
        };
        constexpr static bool value =
               std::is_constructible<helper,
               typename std::add_rvalue_reference<helper>::type> :: value;
        constexpr operator bool () const { return value; }
};

更确切地说,无论一个类是否具有复制构造函数T(const T&),该特征仍然能够检测该类是否还具有移动构造函数T(T&&)

诀窍是派生一个非常简单的类helper,它有两个基础而没有其他方法/构造函数。如果 all 它的基数都有移动构造函数,那么这样的派生类只会有一个隐式移动构造函数。类似于复制构造函数。第一个基类move_not_copy没有复制构造函数,因此helper将没有复制构造函数。但是,helper仍然可以选择一个隐式定义的移动构造函数,当且仅当T具有这样的构造函数时。因此,helper将具有零构造函数或一个构造函数(移动构造函数),这取决于T是否具有移动构造函数。


<强> 测试 即可。这是四种类型的表,显示了所需的行为。一个完整的程序测试它在ideone,但正如我之前所说,它在ideone上得到了错误的结果,因为它们正在使用旧的g ++。

               Copy is_copy_constructible 1  is_move_constructible 1  has_move_constructor 0
           MoveOnly is_copy_constructible 0  is_move_constructible 1  has_move_constructor 1
               Both is_copy_constructible 1  is_move_constructible 1  has_move_constructor 1
CopyWithDeletedMove is_copy_constructible 1  is_move_constructible 0  has_move_constructor 0

该标准对此有何评价?我在阅读cppreference后得到了这个想法,特别是:

  

如果以下任何,则T类的隐式声明或默认移动构造函数被定义为已删除:

     

...

     

T具有无法移动的直接或虚拟基类(已删除,不可访问或不明确的移动构造函数)

     

...

我假设类似的东西适用于复制构造函数。

答案 4 :(得分:1)

当移动构造函数和复制构造函数都存在时,您可以引入有意的歧义错误。这允许我们测试移动构造函数的存在。

近年来,随着编译器的变化,不同的解决方案起作用然后中断。这与clang 3.5.0一起使用。我也希望它能在旧版和新版编译器上运行 - 但我不是标准专家。

此外,这个答案需要更多的工作来完成它,但我已经测试了基本的想法。

首先,如果有复制构造函数,很容易判断。如果没有复制构造函数,那么很容易判断是否存在移动构造函数。当复制构造函数时,挑战是测试是否还有 移动构造函数。这是我将在此重点关注的挑战。

因此,只考虑具有复制构造函数的类型并测试是否存在移动构造函数就足够了。对于这个问题的其余部分,我将假设存在一个复制构造函数。


我通过在存在两种构造函数时强制出现歧义错误来测试移动构造函数,然后(ab)使用SFINAE来测试是否存在这种歧义。

换句话说,我们的挑战是测试以下类型之间的差异:

struct CopyOnly { 
    CopyOnly (const CopyOnly&);  // just has a copy constructor
};
struct Both { 
    Both (const Both&);          // has both kinds of constructor
    Both (Both&&);     
};

为此,首先定义一个声称能够将自身转换为两种引用的Converter<T>类。 (我们永远不需要实现这些)

template<typename T>
struct Converter { 
    operator T&& ();
    operator const T& ();
};

其次,请考虑以下几点:

Converter<T> z;
T t(z);

第二行正在尝试构建T。如果TCopyOnly,则t将通过复制构造函数生成,并且传递给复制构造函数的相关引用将从operator const CopyOnly &() Converter<CopyOnly>方法中提取1}}。到目前为止,这是非常标准的。 (我想?)

但是,如果TBoth,即它也有一个移动构造函数,则会出现歧义错误。 T的两个构造函数都可用,因为转换器可用于两者(来自z的转换器),因此存在歧义。 (任何能够证实这一点的语言律师都是完全标准的吗?)

此逻辑也适用于new T( Converter<T>{} )。当且仅当T没有移动构造函数时,此表达式才具有类型。因此,我们可以将decltype包裹起来并在SFINAE中使用它。

我关闭了baz<T>的两个重载。选择的重载将取决于TCopyOnly还是Both。仅当new T( Converter<T>{} )定义良好时,即如果没有歧义错误,即如果没有移动构造函数,则第一个重载才有效。您可以为每个重载提供不同的返回类型,以便在编译时提供此信息。

template<typename T>
std:: true_type
baz (decltype( new T( Converter<T>{} )   )) {
    cout << __LINE__ << endl;
    return {};
}

template<typename U>
std:: false_type
baz ( 
        const volatile // const volatile to tie break when both forms of baz are available
        U *) { 
    cout << __LINE__ << endl;
    return {};
}
应该像这样调用{p> baz

baz<JustCopy>((JustCopy*)nullptr);
baz<Both>((Both*)nullptr);

你可以把它包装成这样的东西:

template<typename T>
struct has_move_constructor_alongside_copy {
    typedef decltype(baz<T>((T*)nullptr)) type;
};

要做好整理工作还有很多工作要做,而且我确信SFINAE专家可以大大改善它(请做!)。但我认为这解决了主要问题,当我们已经知道存在复制构造函数时,测试移动构造函数的存在。

答案 5 :(得分:1)

我拿了Aaron McDaid's last answer并将其包裹在下面的结构中。他的答案中的代码对我没有用,这对clang 3.6和MSVC2013都有效。

template <typename T>
struct has_move_constructor_alongside_copy {
  typedef char yes[1];
  typedef char no[2];

  struct AmbiguousConverter {
    operator T&& ();
    operator const T& ();
  };

  template <typename C>
  static no& test(decltype( new C( AmbiguousConverter{} )));

  template <typename>
  static yes& test(...);

  static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};