为什么不允许在非推导的上下文中使用基类定义,以及如何解决这个问题?

时间:2017-05-04 11:12:22

标签: c++ templates struct

我有以下代码:

#include <iostream>

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct Derived : Base<T>
{
    //uncommmenting the below cause compiler error
    //using Alias = Type;
};

int main()
{
    Derived<void>::Type b = 1;
    std::cout << b << std::endl;

    return 0;
}

现在,Type类型名称Derived可用于推导上下文中的b - 如Type完全有效的声明所示。但是,如果我尝试在Derived本身的声明中引用Type,那么我会收到编译器错误,告诉我Alias没有命名类型(例如,如果定义Type未被注释。

我想这与编译器无法检查Derived是否可以在基类中解析T的定义时从基类中引入时有所作为。参数Base的特定实例化。在这种情况下,这很令人沮丧,因为Type 总是定义T而不管Derived。所以我的问题有两个:

1)。为什么地球会发生这种情况?我的意思是为什么编译器在实例化上下文之外根本不解析using Alias = typename Base<T>::Type(我猜是非推导的上下文),如果不这样做会避免这些'伪造'编译器错误?也许这是有充分理由的。标准中规定这必须发生的规则是什么?

2)。对于这类问题,有什么好的解决方法?我有一个真实的案例,我需要在派生类的定义中使用基类类型,但是由于这个问题我无法这样做。我想我正在寻找某种“隐藏在非推断上下文”解决方案之外的解决方案,我通过在模板化类或其他方面放置所需的定义/ typedef来防止这个编译器“首次通过”。

编辑:正如下面的一些答案所指出的那样,我可以使用Derived。我应该从一开始就说,我知道这是有效的。但是,由于两个原因,它并不完全令人满意:1)它根本不使用继承层次结构(Base不必从using派生来实现这一点),而且我是正是尝试使用我的基类层次结构中定义的类型和2)真实案例实际上有几层继承。如果我想从几层中提取某些东西,这变得非常难看(我需要引用一个非直接的祖先,或者在每一层都重复 <html> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script> <body> <p>Click the table headers to change the sorting order:</p> <div ng-app="myApp" ng-controller="namesCtrl"> <p> <button type="button" ng-click="remove($index)">Delete Selected</button> </p> <table border="1" width="100%"> <tr> <th> <input type="checkbox" ng-model="selectAll" ng-click="checkAll()" /> </th> <th></th> <th>Sl no</th> <th ng-click="orderByMe('name')">Name</th> <th ng-click="orderByMe('country')">Country</th> <th>Delete</th> </tr> <tr ng-repeat="x in names | orderBy:myOrderBy"> <td> <input type="checkbox" ng-model="x.select" ng-click="xsetting(x)" /> </td> <td>{{x.select}}</td> <td>{{$index+1}}</td> <td>{{x.name}}</td> <td>{{x.country}}</td> <td> <button type="button" ng-click="Delete(x)">Delete</button> </td> </tr> </table> </div> <script> angular.module('myApp', []).controller('namesCtrl', ['$scope', 'filterFilter', function($scope, filterFilter) { $scope.names = [{ name: 'Jani', country: 'Norway' }, { name: 'Carl', country: 'Sweden' }, { name: 'Margareth', country: 'England' }, { name: 'Hege', country: 'Norway' }, { name: 'Joe', country: 'Denmark' }, { name: 'Gustav', country: 'Sweden' }, { name: 'Birgit', country: 'Denmark' }, { name: 'Mary', country: 'England' }, { name: 'Kai', country: 'Norway' }]; //for sorting the rows $scope.orderByMe = function(x) { $scope.myOrderBy = x; } //single row deletion $scope.Delete = function(x) { $scope.names.splice($scope.names.indexOf(x), 1); }; //selecting all checkboxes in the table $scope.checkAll = function() { angular.forEach($scope.names, function(x) { x.select = $scope.selectAll; }); }; //for selecting and deleting checked items $scope.remove = function() { $scope.names = filterFilter($scope.names, function(x) { return !x.select; }); }; }]); </script> </body> </html> ,直到我到达我需要它的那个)

6 个答案:

答案 0 :(得分:6)

由于type位于“依赖范围”,您可以像这样访问它:

typename Base<T>::Type

然后应该像这样定义Alias

using Alias = typename Base<T>::Type;

请注意,此时编译器不知道Base<T>::type是否描述了成员变量或嵌套类型,这就是为什么需要关键字typename

<强>层

您无需在每个图层重复此定义,以下是一个示例link

template <typename T>
struct intermediate : Base<T>
{
    // using Type = typename Base<T>::Type; // Not needed
};

template <typename T>
struct Derived : intermediate<T>
{
    using Type = typename intermediate<T>::Type;
};

<强>更新

您也可以使用自己的类,这依赖于使用unknown specializations

template <typename T>
struct Derived : Base<T>
{
    using Type = typename Derived::Type; // <T> not required here.
};

答案 1 :(得分:4)

问题是Base<T>是一个依赖的基类,并且可能存在特殊化,其中Type不再被定义。比如你有一个像

这样的专业化
template<>
class Base<int>
{}; // there's no more Type here

编译器无法事先知道(技术上它在模板实例化之前无法知道),特别是如果在不同的转换单元中定义了特化。因此,语言设计者选择采用简单的方法:每当你提到依赖的东西时,你需要明确地指明这一点,就像你的情况一样

using Alias = typename Base<T>::Type;

答案 2 :(得分:3)

  

我想这与编译器在查询参数T的特定实例化的上下文之外的Derived定义时无法检查是否可以从基类中提取Type有关。 / p>

  

在这种情况下,这是令人沮丧的,因为Base始终定义Type而不管T

但总的来说,检测这是否属实是完全不可行的,如果语言的语义在真实时发生了变化,则会非常混乱。

  

也许这是有充分理由的。

C ++是一种通用的编程语言,而不是针对该程序优化的Smeeheey正在使用的编程语言。 :)

  

标准中规定这必须发生的规则是什么?

就是这样:

  

[C++14: 14.6.2/3]:在类或类模板的定义中,如果基类依赖于模板参数,则在非限定名称查找期间不会检查基类范围。类模板或成员的定义点,或者在类模板或成员的实例化过程中。 [..]

  

对于这类问题,有什么好的解决方法?

你已经知道 - 资格:

using Alias = typename Base<T>::Type;

答案 3 :(得分:2)

定义模板时,有时事情需要更加明确:

using Alias = typename Base<T>::Type;

Rougly说:模板是各种各样的蓝图。在模板实例化之前,实际上什么都不存在。

在做同样的事情时,但在非模板上下文中,C ++编译器会试图找出Type是什么。它会尝试在基类中找到它,并从那里开始。因为一切都已经宣布,而且事情几乎都是干涸的。

这里,在实例化模板之前,基类并不存在。如果您已经了解了模板特化,那么您应该意识到基类实际上可能没有Type成员,当模板被实例化时,如果对后来定义的基类进行了专门化,这将覆盖整个事情,并将其内外转。

因此,当在这种情况下遇到一个普通的旧Type时,编译器不能做出很多假设。它不能假设它可以查看任何已定义的模板基类,因为当事情开始凝固时,这些基类实际上可能看起来不像编译器认为它们看起来的样子;所以你明确地为编译器拼出所有内容,并在这里告诉编译器你正在尝试做什么。

答案 4 :(得分:2)

您不能在非推断的上下文中使用基类类型。 C ++拒绝假设未绑定的名称引用基类中的内容。

template <typename T>
struct Base {
  using Type = int;
};
template<>
struct Base<int> {};

using Type=std::string;

template <typename T>
struct Derived : Base<T> {
  using Alias = Type;
};

现在让我们看看这里发生了什么。 Type中可以看到Derived - 一个全球性的DerivedType应该使用与否吗?

根据&#34;在实例化之前解析任何内容&#34;,我们使用全局T当且仅当intBase时,由于{{1从中删除Type的专门化。

遵循该规则我们遇到的问题是,我们可以在模板实例化之前基本上没有错误诊断,因为基类可以取代几乎任何东西的含义!致电abs?可以成为父母的一员!提一个类型?可能来自父母!

这会强制模板基本上是宏;在实例化之前无法进行有意义的解析。轻微的拼写错误可能导致大量不同的行为。如果不创建测试实例,几乎任何模板中的不正确都无法诊断。

拥有可以检查正确性的模板意味着当您想要使用父类类型和成员时,您必须说您正在这样做。

答案 5 :(得分:0)

作为关于这一点的部分回应

  

[T]他很令人沮丧,因为Base始终定义Type而不管T

我说:不,不是。

请考虑以下示例与您的不同之处仅在于Base<void>的一行定义和Alias的定义:

#include <iostream>

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct Derived : Base<T>
{
    using Alias = typename Base<T>::Type; // error: no type named 'Type' in 'struct Base<void>'
};

template<> struct Base<void> {};

int main()
{
    Derived<void>::Type b = 1;
    std::cout << b << std::endl;

    return 0;
}

template <typename T> struct Derived : Base<T>的上下文中,没有Type存在的保证。您必须显式地告诉您的编译器,而Base<T>::Type是一种类型(带typename),如果您没有通过此合同,则最终会出现编译错误。