模板代码中的类型不完整

时间:2014-03-31 19:21:14

标签: c++ templates c++11 incomplete-type

假设我们有两种类型(完整和不完整):

struct CompleteType{};

struct IncompleteType;

我们还有模板代码:

#include <type_traits>

template <typename = X(T)>
struct Test : std::false_type {};

template <>
struct Test<T> : std::true_type {};

T可以是CompleteTypeIncompleteTypeX(T)可以是Tdecltype(T())decltype(T{})(假设{ {1}}是一个宏。)

此代码以下列方式使用:

X(T)

下面你可以看到不同的编译器如何处理这些代码:


clang 3.4

std::cout << std::boolalpha << Test<>::value << std::endl;
  1. X(T) \ T CompleteType IncompleteType T true true decltype(T()) true --- (1, 2) decltype(T{}) true --- (1, 2) 甚至在不完整类型的模板类声明中给出(error: invalid use of incomplete type 'IncompleteType'decltype(T()),但不是简单decltype(T{}))而不使用{{ 1}}在代码中。

  2. T


  3. g ++ 4.8.1

    Test<>::value

    vc ++ 18.00.21005.1

    error: too few template arguments for class template 'Test'
    1. X(T) \ T CompleteType IncompleteType T true true decltype(T()) true true decltype(T{}) true true

    2. X(T) \ T CompleteType IncompleteType T true true decltype(T()) true --- (1) decltype(T{}) true --- (2)


    3. 什么编译器按照标准行事?请注意,error C2514: 'IncompleteType' : class has no constructors之类的简单字符串不能在error C2440: '<function-style-cast>' : cannot convert from 'initializer-list' to 'IncompleteType' Source or target has incomplete type的所有变体的所有编译器上编译( vc ++除外) std::cout << typeid(X(IncompleteType)).name() << std::endl;)。

1 个答案:

答案 0 :(得分:11)

我认为Clang和MSVC的行为在这种情况下与标准一致。我认为海湾合作委员会在这里采取了一些捷径。

让我们先把一些事实放在桌面上。 decltype表达式的操作数是所谓的未评估的操作数,由于它们最终永远不会被评估,所以它们的处理方式略有不同。

特别是,对完成类型的要求较少。基本上,如果您有任何临时对象(作为表达式中涉及的函数或运算符中的参数或返回值),则不需要完成它们(请参阅第5.2.2 / 11和7.1.6.2/5节)。但是这只会解除通常的限制&#34;你不能声明一个不完整类型的对象&#34;但它不会解除对不完整类型的其他限制,这就是&#34;你不能调用成员函数不完整类型&#34;。这就是踢球者。

表达式decltype(T())decltype(T{}),其中T不完整,必须查找T类型的构造函数,因为它是&#39;该类的sa(特殊)成员函数。它只是一个构造函数调用产生一点歧义的事实(即它只是创建一个临时对象?还是它调用构造函数?)。如果是任何其他成员职能,就不会有争论。幸运的是,该标准确实解决了这个争论:

  

12.2 / 1

     

即使临时对象的创建未被评估(Clause   5)或以其他方式避免(12.8),所有语义限制都应如此   被尊重,好像临时对象已经创建,之后   销毁。 [注意:即使没有调用析构函数或者   复制/移动构造函数,所有语义限制,如   可访问性(第11条)以及是否删除该功能   (8.4.3),应予以满足。但是,在特殊情况下   函数调用用作decltype-specifier(5.2.2)的操作数,no   临时介绍,所以上述内容不适用于   任何此类函数调用的prvalue。 - 结束说明]

最后一句可能有点令人困惑,但这只适用于函数调用的返回值。换句话说,如果你有T f();函数,并声明decltype(f()),那么T不需要是完整的,或者对是否有可用的构造函数/析构函数进行任何语义检查。可以使用它。

事实上,整个问题正是为什么有std::declval实用程序,因为当您无法使用decltype(T())时,您只能使用decltype(std::declval<T>())declval是只是一个返回类型为T的prvalue的(假)函数。但当然,declval旨在用于不太重要的情况,例如decltype( f( std::declval<T>() ) )其中f将是一个采用T类型对象的函数。 declval并不要求类型完整(参见第20.2.4节)。这基本上就是解决这个问题的方法。

因此,就GCC的行为而言,我认为它需要一个捷径,因为它试图找出T()T{}的类型。我认为,只要GCC发现T引用了类型名称(不是函数名称),就会推断出这是一个构造函数调用,因此,无论查找结果是什么,实际的构造函数都是如此。被调用时,返回类型将为T(严格来说,构造函数不具有返回类型,但您理解我的意思)。这里的要点是,在未评估的表达中,这可能是一个有用的(更快的)捷径。但据我所知,这不是符合标准的行为。

如果GCC允许CompleteType构造函数被删除或私有,那么这也与上面引用的标准段落直接矛盾。即使未评估表达式,编译器也需要在该情况下强制执行所有语义限制。

  

请注意,像std::cout << typeid(X(IncompleteType)).name() << std::endl;这样的简单字符串不能在所有X变量编译器上编译(vc ++和X(T)== T除外)。

这是预期的(除了MSVC和X(T)== T)。 typeidsizeof运算符类似于decltype,因为它们的操作数未被评估,但是,它们都有额外的要求,即结果表达式的类型必须是完整的类型。可以想象,编译器可以针对不完整类型(或至少使用部分类型信息)解析typeid,但标准需要完整类型,以便编译器不必执行此操作。我想这就是MSVC正在做的事情。

因此,在这种情况下,T()T{}案例失败的原因与decltype相同(正如我刚才解释的那样),X(T) == T案例失败因为typeid需要一个完整的类型(但MSVC设法解除了这个要求)。并且在GCC上,由于typeid要求所有X(T)个案件的完整类型(即,短期GCC在{{1}的情况下不会影响结果,因此失败}或sizeof)。

所以,总而言之,我认为Clang是三者中最符合标准的(不采取捷径或扩展)。