【问题标题】:Why does GCC speak about a dependent base?为什么 GCC 谈到依赖基础?
【发布时间】:2018-12-05 14:14:24
【问题描述】:

这是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再次编译程序。

GCC 还是不高兴:

$ 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 方法:来自&lt;algorithm&gt; 的方法,它是通过

意外包含的
. /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),但为什么它会抱怨依赖基数? struct pf 与继承无关。可能是因为这种可见性问题最常出现在依赖库中吗?

最后,正确的用法竟然是:

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

【问题讨论】:

  • 我认为你的猜测是正确的。这只是关于这种错误的一个可能原因的注释。这并不意味着您的具体示例因此而失败。
  • std::declval&lt;pf&amp;&gt;(),使用右值似乎是错误的。
  • gcc 说由于 ADL 的依赖基础。它使用 std::basic_string 范围及其基本范围进行查找。
  • 顺便说一句,如果你把fill放在构造函数之前,你会得到另一个有趣的错误:“cannot call fill without object”。
  • 你确定你的修复是正确的吗? ideone.com/0BBmqz

标签: c++ c++11


【解决方案1】:

乍一看,这似乎是 GCC 中的一个以上错误。

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

[basic.scope.class]¶1

类中声明的名称的潜在范围 [...] 还包括所有函数体、默认参数、异常规范brace-or-equal-initializers 该类中的非静态数据成员(包括嵌套类中的此类内容)。

因此,在成员函数声明的noexcept 子句中,类的所有成员都在范围内,无论它们的声明顺序如何。

但这还不足以让我们自己相信您的代码格式正确。我们在一个类模板中,所以接下来我们需要考虑两阶段查找:

[temp.dep]¶1

在一个表达式中:

后缀表达式 ( 表达式列表选择 )

后缀表达式是一个unqualified-idunqualified-id表示一个依赖名 如果

  • (1.1) 表达式列表中的任何表达式都是包扩展,
  • (1.2) 表达式列表中的任何表达式都是依赖于类型的表达式,或者
  • (1.3) 如果 unqualified-idtemplate-id,其中任何模板参数都依赖于模板参数。

此类名称是未绑定的,并且在模板定义的上下文和实例化点的上下文中都在模板实例化点进行查找。

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

现在我们已经说服自己代码格式正确。我们可以简单地提交一个错误并继续前进。但让我们继续挖掘,看看我们对正在发生的事情的了解程度。


[temp.dep]¶3

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

错误消息似乎表明 GCC (错误地)将 fill 的封闭类视为 fill 的依赖基类,因此应用了 [temp.dep]¶3 的措辞。

在依赖库中引用名称的常用方法是通过qualified-id(例如pf::fill)或类成员访问表达式(例如this-&gt;fill)来引用它们。那么当我们尝试其中任何一种方法作为解决方法时会发生什么?

编写this-&gt;fill 似乎可以在您的示例代码中使用,但正如@n.m 所指出的那样。在 cmets 中,这很脆弱,在更小的示例中不起作用,会产生错误“在顶层无效使用 'this'”。

编写pf::fill 会产生错误“无法调用没有对象的成员函数”。这可能与 this-&gt;fill 失败的原因相同:如果 this 无效,那么将 id-expression 转换为隐式成员访问表达式也一定是无效的。

[expr.prim.general]/3

如果声明声明类X成员函数或成员函数模板,则表达式this是“指向cv-qualifier-seq X" 介于可选的cv-qualifer-seq函数定义的结尾成员声明符声明符

所以this 可以有效地出现在异常规范中,而 GCC 拒绝它是错误的,从表面上看,这是一个与我们已经诊断出的错误不同的错误.最后一个已经被称为52869,但是与错误修复一起提交的测试用例未能执行noexcept 出现在它所指的成员函数的声明之前的情况,就像你的情况一样。所以不清楚你的第一组错误是否真的是同一个错误的一部分。

【讨论】:

    猜你喜欢
    • 2012-03-28
    • 2015-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-21
    • 1970-01-01
    相关资源
    最近更新 更多