具有自动返回类型扣除的朋友功能模板无法访问私有成员

时间:2015-10-01 13:57:48

标签: c++ templates language-lawyer c++14 friend

很抱歉这个问题的标题有多复杂;我试图描述我为这个问题构建的最小SSCCE。

我有以下代码:

#include <iostream>

namespace fizz
{
    template<typename... Ts>
    class bar
    {
    public:
        template<int I, typename... Us>
        friend auto foo(const bar<Us...> &);

    private:
        int i = 123;
    };

    template<int I, typename... Ts>
    auto foo(const bar<Ts...> & b)
    {
        return b.i;
    }
}

int main()
{
    std::cout << fizz::foo<1>(fizz::bar<int, float>{});
}

此代码compiles with GCC 5.2doesn't with Clang 3.7

main.cpp:19:18: error: 'i' is a private member of 'fizz::bar<int, float>'
        return b.i;
                 ^
main.cpp:25:24: note: in instantiation of function template specialization 'fizz::foo<1, int, float>' requested here
    std::cout << fizz::foo<1>(fizz::bar<int, float>{});
                       ^
main.cpp:13:13: note: declared private here
        int i = 123;
            ^

但是,如果稍微更改代码(虽然对我来说不是很有用,因为在实际代码中这会引入大量的样板):

#include <iostream>

namespace fizz
{
    template<typename... Ts>
    class bar
    {
    public:
        template<int I, typename... Us>
        friend int foo(const bar<Us...> &);

    private:
        int i = 123;
    };

    template<int I, typename... Ts>
    int foo(const bar<Ts...> & b)
    {
        return b.i;
    }
}

int main()
{
    std::cout << fizz::foo<1>(fizz::bar<int, float>{});
}
突然works with that Clang 3.7

不同之处在于,在没有使用Clang编译的代码版本中,友元函数模板使用C ++ 14 auto返回类型推导,而工作模式明确表示返回{ {1}}。 int返回类型扣除的其他变体也会出现同样的问题,例如autoauto &&

哪个编译器是对的?请提供一些标准报价以支持答案,因为很可能需要为一个(...希望不是两个)编译器提交错误...或者标准缺陷,如果两者都是正确的(这不会&# 39;这是第一次)。

2 个答案:

答案 0 :(得分:6)

我相信这是一个铿锵的错误。我想从这个方向接近它。与具有指定的返回类型相比,auto占位符类型添加了什么皱纹?来自[dcl.spec.auto]:

  

占位符类型可以与 decl-specifier-seq 中的函数声明符一起出现, type-specifier-seq conversion-function-id ,或 trailing-return-type ,在此类声明符有效的任何上下文中。如果函数声明符包含 trailing-return-type (8.3.5),则 trailing-return-type 指定声明的返回类型   功能。否则,函数声明符应声明一个函数。如果声明了返回类型   该函数包含占位符类型,函数的返回类型是从return语句推导出来的   在函数体中,如果有的话。

auto可以出现在foo的声明和定义中,并且有效。

  

如果需要具有undeduced占位符类型的实体类型来确定表达式的类型,   该计划格式不正确。但是,在函数中看到return语句后,返回类型   从该语句推导出的可以在函数的其余部分中使用,包括在其他return语句中。 [例如:

auto n = n;              // error, n’s type is unknown
auto f();
void g() { &f; }         // error, f’s return type is unknown
auto sum(int i) {
  if (i == 1)
    return i;            // sum’s return type is int
  else
    return sum(i-1)+i;   // OK, sum’s return type has been deduced
}
     

-end example]

我们第一次需要使用确定表达式的类型时,函数的返回类型已经从return定义中的foo()推导出来,所以这仍然有效

  

具有使用a的声明返回类型的函数或函数模板的重新声明或特化   占位符类型也应使用该占位符,而不是推断类型。

我们在两个地方都使用auto,因此我们也不违反此规则。

简而言之,有几种方法可以将特定的返回类型与占位符返回类型与函数声明区分开来。但是示例中auto的所有用法都是正确的,因此命名空间范围foo应该被视为对类模板{{1}中第一个声明的friend auto foo的重新声明和定义。 }}。 clang接受前者作为返回类型bar但不是int的重新声明的事实,并且auto没有相关的不同,这肯定表明这是一个错误。

此外,如果您放弃auto模板参数以便可以调用int I不合格,则clang会将调用报告为不明确的:

foo

因此我们在同一名称空间中有两个函数模板std::cout << foo(fizz::bar<int, float>{}); main.cpp:26:18: error: call to 'foo' is ambiguous std::cout << foo(fizz::bar<int, float>{}); ^~~ main.cpp:10:21: note: candidate function [with Us = <int, float>] friend auto foo(const bar<Us...> &); ^ main.cpp:17:10: note: candidate function [with Ts = <int, float>] auto foo(const bar<Ts...>& b) ^ (因为来自[namespace.memdef] foo friend声明将放置它在最近的封闭命名空间中,它采用相同的参数并具有相同的返回类型(foo)?这应该是不可能的。

答案 1 :(得分:0)

看来您的第一个示例应该可行。 C ++ 14(7.1.6.4 p12)中有一个声明:

  

具有使用a的声明返回类型的函数或函数模板的重新声明或特化   占位符类型也应使用该占位符,而不是推断类型。 [例如:

。 。

template <typename T> struct A {
    friend T frf(T);
};
auto frf(int i) { return i; } // not a friend of A<int>

示例的原因似乎是解释为了使声明匹配(并使定义的函数成为朋友),struct A中的frf声明也需要使用auto。这对我来说意味着允许使用具有自动返回类型的朋友声明并稍后定义友元函数(并且还使用自动)。对于成员函数模板,我找不到任何会使其工作方式不同的内容,例如在您的示例中。