为什么GCC会说一个依附的基础?

时间:2018-12-05 14:14:25

标签: c++ c++11

这是When is a class member visible?

的延续

通过将pk_的声明移到开头使类在GCC编译之后,我尝试使用它:

#include <string>
#include <map>
#include <type_traits>

using from_type = std::map<std::string, std::string>;

template<typename PK, size_t N>
struct pf {
public:
    PK pk_;

    pf(from_type const& a, std::string const& pkn) noexcept(noexcept(fill(pk_, std::string{})))
    : map_(a) // GCC 4.8 requires ()s for references
    , pk_{ [&]{ fill(pk_, pkn); return pk_; }() }
    {
    }

    template<typename prop_t>
    typename std::enable_if<
        std::is_integral<typename std::decay<prop_t>::type>::value,
        pf<PK, N>>::type const&
    fill(prop_t& , std::string const& , prop_t  = 0) const noexcept(false);

    pf<PK, N> const&
    fill(std::string& , std::string const&) const noexcept;
protected:
    from_type const& map_;
    uint32_t aieee;

};

std::string k;
from_type m;

int i;
std::string s;

static_assert(!noexcept(pf<int        , 42>{m, k}), "int could throw");
static_assert( noexcept(pf<std::string, 17>{m, k}), "string shouldn't throw");

clang 4.0、6.0和trunk再次编译了程序。

海湾合作委员会仍然不高兴:

$ g++-99 -Wall -pedantic -Wextra -Wformat=2 -std=c++14 pf.cpp
pf.cpp: In instantiation of ‘pf<PK, N>::pf(const from_type&, const string&) [with PK = int; long unsigned int N = 42; from_type = std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; std::string = std::__cxx11::basic_string<char>]’:
pf.cpp:38:49:   required from here
pf.cpp:12:74: error: no matching function for call to ‘fill(int&, std::__cxx11::basic_string<char>)’
   12 |     pf(from_type const& a, std::string const& pkn) noexcept(noexcept(fill(pk_, std::string{})))
      |                                                                      ~~~~^~~~~~~~~~~~~~~~~~~~
In file included from /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/char_traits.h:39,
                 from /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/string:40,
                 from pf.cpp:1:
/usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/stl_algobase.h:742:5: note: candidate: ‘template<class _ForwardIterator, class _Tp> void std::fill(_ForwardIterator, _ForwardIterator, const _Tp&)’
  742 |     fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value)
      |     ^~~~
/usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/stl_algobase.h:742:5: note:   template argument deduction/substitution failed:
pf.cpp:12:74: note:   deduced conflicting types for parameter ‘_ForwardIterator’ (‘int’ and ‘std::__cxx11::basic_string<char>’)
   12 |     pf(from_type const& a, std::string const& pkn) noexcept(noexcept(fill(pk_, std::string{})))
      |                                                                      ~~~~^~~~~~~~~~~~~~~~~~~~
pf.cpp: In instantiation of ‘pf<PK, N>::pf(const from_type&, const string&) [with PK = std::__cxx11::basic_string<char>; long unsigned int N = 17; from_type = std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; std::string = std::__cxx11::basic_string<char>]’:
pf.cpp:39:49:   required from here
pf.cpp:12:74: error: no matching function for call to ‘fill(std::__cxx11::basic_string<char>&, std::__cxx11::basic_string<char>)’
In file included from /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/char_traits.h:39,
                 from /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/string:40,
                 from pf.cpp:1:
/usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/stl_algobase.h:742:5: note: candidate: ‘template<class _ForwardIterator, class _Tp> void std::fill(_ForwardIterator, _ForwardIterator, const _Tp&)’
  742 |     fill(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __value)
      |     ^~~~
/usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/stl_algobase.h:742:5: note:   template argument deduction/substitution failed:
pf.cpp:12:74: note:   candidate expects 3 arguments, 2 provided
   12 |     pf(from_type const& a, std::string const& pkn) noexcept(noexcept(fill(pk_, std::string{})))
      |                                                                      ~~~~^~~~~~~~~~~~~~~~~~~~
pf.cpp:39:16: error: static assertion failed: string shouldn't throw
   39 | static_assert( noexcept(pf<std::string, 17>{m, k}), "string shouldn't throw");
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

直到我看到第一个问题的答案之前,这令人困惑(谁说了关于迭代器的话?)。编译器看不到fill成员,因此尝试了唯一可用的fill方法:<algorithm>中的方法,偶然地通过

包含了该方法。
. /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/string
.. /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/char_traits.h
... /usr/local/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/c++/bits/stl_algobase.h

因此,我将fill成员重命名为fillz(包括noexcept运算符中的成员),结果是:

$ g++-99 -Wall -pedantic -Wextra -Wformat=2 -std=c++14 pf.cpp
pf.cpp: In instantiation of ‘pf<PK, N>::pf(const from_type&, const string&) [with PK = int; long unsigned int N = 42; from_type = std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; std::string = std::__cxx11::basic_string<char>]’:
pf.cpp:38:49:   required from here
pf.cpp:12:75: error: ‘fillz’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
   12 |     pf(from_type const& a, std::string const& pkn) noexcept(noexcept(fillz(pk_, std::string{})))
      |                                                                      ~~~~~^~~~~~~~~~~~~~~~~~~~
pf.cpp:12:75: note: declarations in dependent base ‘pf<int, 42>’ are not found by unqualified lookup
pf.cpp:12:75: note: use ‘pf::fillz’ instead
pf.cpp:12:75: error: cannot call member function ‘const typename std::enable_if<std::is_integral<typename std::decay<prop_t>::type>::value, pf<PK, N> >::type& pf<PK, N>::fillz(prop_t&, const string&, prop_t) const [with prop_t = int; PK = int; long unsigned int N = 42; typename std::enable_if<std::is_integral<typename std::decay<prop_t>::type>::value, pf<PK, N> >::type = pf<int, 42>; std::string = std::__cxx11::basic_string<char>]’ without object
pf.cpp: In instantiation of ‘pf<PK, N>::pf(const from_type&, const string&) [with PK = std::__cxx11::basic_string<char>; long unsigned int N = 17; from_type = std::map<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; std::string = std::__cxx11::basic_string<char>]’:
pf.cpp:39:49:   required from here
pf.cpp:12:75: error: ‘fillz’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
pf.cpp:12:75: note: declarations in dependent base ‘pf<std::__cxx11::basic_string<char>, 17>’ are not found by unqualified lookup
pf.cpp:12:75: note: use ‘pf::fillz’ instead
pf.cpp:12:75: error: cannot call member function ‘const pf<PK, N>& pf<PK, N>::fillz(std::string&, const string&) const [with PK = std::__cxx11::basic_string<char>; long unsigned int N = 17; std::string = std::__cxx11::basic_string<char>]’ without object
pf.cpp:39:16: error: static assertion failed: string shouldn't throw
   39 | static_assert( noexcept(pf<std::string, 17>{m, k}), "string shouldn't throw");
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

很明显,GCC仍然看不到该成员(现在命名为fillz),但是为什么它抱怨依赖的基数?结构pf与继承无关。可能是因为这种可见性问题最常出现在依赖的基础上?

最后,正确的用法是:

noexcept(noexcept(std::declval<pf&>().fill(std::declval<PK&>(), std::string{})))

1 个答案:

答案 0 :(得分:1)

乍一看,这似乎可能是GCC中的多个错误。

(所有标准引号均来自C ++ 14,因为您正在使用-std=c++14进行编译。所有粗体均已添加。)

  

[basic.scope.class]¶1

     

在类中声明的名称的潜在范围 [...]还包括所有函数体,默认参数, 异常规范 括号或相等初始化器(包括嵌套类中的此类内容)。

因此,在成员函数声明的noexcept子句中,该类的所有成员均在范围内,而与声明它们的顺序无关。

但这还不足以说服自己您的代码格式正确。我们处于类模板中,因此接下来我们需要考虑两阶段查找:

  

[temp.dep]¶1

     

以以下形式的表达式:

     
    

后缀表达式 ( 表达式-listopt )

  
     

其中后缀表达式不合格ID 不合格ID 表示相关名称< / em>如果

     
      
  • (1.1) expression-list 中的任何表达式都是一个包扩展,
  •   
  • (1.2) 表达式列表中的任何表达式都是类型相关的表达式,或者
  •   
  • (1.3),如果 unqualified-id template-id ,其中任何模板参数都取决于模板参数。
  •   
     

这样的名称是不受限制的,并且在模板定义的上下文和实例化点的上下文中都在模板实例化的位置进行查找。

因此fillfill(pk_, std::string{})中的从属名称,因为pk_是类型相关的,因此在实例化模板时将在上下文中查找fill模板本身及其实例化的位置。由于ADL和其中一个参数是std::string的事实,std名称空间包含在查找中,从而导致有关std::fill的消息。但是查找也应该找到成员函数fill,如先前所讨论。

现在,我们已经使自己确信代码格式正确。我们可以简单地提交一个错误然后继续。但是,让我们继续挖掘以了解我们对正在发生的事情的了解。


  

[temp.dep]¶3

     

在类或类模板的定义中,如果基类依赖于 template-parameter ,则在非限定名称查找期间也不会检查基类范围在类模板或成员的定义时或在类模板或成员的实例化期间。

错误消息似乎表明(错误地)GCC认为fill的封闭类是fill的从属,因此使用了[temp.dep]¶3的措词

在依赖基中引用名称的通常方法是通过 qualified-id (例如pf::fill)或类成员访问表达式(例如{{1})来引用它们}。那么,当我们尝试使用这些方法中的任何一种作为替代方法时会发生什么?

在您的示例代码中似乎可以写this->fill,但正如@ n​​.m所指出的那样。在评论中,它很脆弱,在最小的示例中不起作用,从而产生错误“在顶层无效使用'this'”。

写入this->fill会产生错误“没有对象就无法调用成员函数”。可能是由于pf::fill失败的原因:如果this->fill无效,则将 id-expression 转换为隐式成员访问表达式也必须无效。< / p>

  

[expr.prim.general] / 3

     

如果声明声明了类this成员函数或成员函数模板,则表达式X 是类型为“指向 cv-qualifier-seq this的指针” 介于可选的 cv-qualifer-seq function-definition < / em> 成员声明器声明器

因此X可以有效地出现在异常规范中,而GCC拒绝该错误是错误的,而且从表面上看,这是一个与我们的错误不同的错误已经被诊断出来。最后一个已经被称为52869,但是与bugfix一起提交的测试用例未能解决this出现在所引用的成员函数声明之前的情况,就像您的情况一样。因此,目前尚不清楚您的第一组错误是否确实属于同一错误。