模板替换和SFINAE中的私有成员访问 [英] Private member access in template substitution and SFINAE

查看:60
本文介绍了模板替换和SFINAE中的私有成员访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

class A { int a; };

template<typename, typename = void>
class test {};

template<typename T>
class test<T,decltype(T::a)> {};

int main() { test<A> a; }

上面的代码在 clang版本3.8.0-2ubuntu4(tags/RELEASE_380/final)上编译时没有错误,但是在 g ++-5(Ubuntu 5.4.1-2ubuntu1)上编译失败〜16.04)5.4.1 20160904 g ++-6(Ubuntu 6.2.0-3ubuntu11〜16.04)6.2.0 20160901 ,错误如下:

The code above compiles without error on clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final), but fails to compile on g++-5 (Ubuntu 5.4.1-2ubuntu1~16.04) 5.4.1 20160904 and g++-6 (Ubuntu 6.2.0-3ubuntu11~16.04) 6.2.0 20160901 with errors like this:

main.cpp: In function ‘int main()’:
main.cpp:9:22: error: ‘int A::a’ is private within this context
 int main() { test<A> a; }
                      ^
main.cpp:1:15: note: declared private here
 class A { int a; };

在两种情况下,我都使用 -std = c ++ 11 进行编译,但是对于 -std = c ++ 14 -std = c ++ 1z .

In both cases I compiled with -std=c++11, but the effect is the same for -std=c++14 and -std=c++1z.

哪个编译器在这里正确?我认为,至少从C ++ 11开始,在模板替换期间访问私有成员将触发SFINAE,这意味着clang是正确的,而gcc不是.我还有其他规则不知道吗?

Which compiler is correct here? I assumed that, at least since C++11, accessing private members during template substitution should trigger SFINAE, implying that clang is correct and gcc is not. Is there some additional rule I am unaware of?

作为参考,我正在考虑当前标准草案N4606的第14.8.2/8条中的注释:

For referencce, I am thinking of the note in §14.8.2/8 of the current standard draft N4606:

如果替换导致无效的类型或表达式,请键入推论失败.无效的类型或表达式将是格式错误,需要诊断,如果使用替代参数.[注意:如果不需要诊断,则程序仍然格式不正确.访问检查是在以下情况下完成的:替代过程.—尾注]

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. — end note ]

两个编译器都接受使用成员函数和成员函数指针:

Using a member function and a member function pointer instead is accepted by both compilers:

class A { void a() {}; };

template<typename, typename = void>
class test {};

template<typename T>
class test<T,decltype(&T::a)> {};

int main() { test<A> a; }

推荐答案

这很有趣!我认为是一个g ++编译器错误,我认为就是这种情况.我已经尝试过使用g ++ 4.9.3和clang 3.7.0对您的代码进行几次更改.

This is very interesting! I think it's a g++ compiler bug and I think that this is what happens. I've tried several alterations of your code with g++ 4.9.3 and clang 3.7.0 .

尽管功能和类模板实例化有一些不同的规则,但我相信这些是模板实例化的一般步骤:

Although there are somewhat different rules to function vs class template instantiation, I believe that these are the general steps to a template instantiation:

  1. 编译器读取带有模板定义的源文件.
  2. 名称查找(可能会触发ADL):通过该过程,名称,在程序中遇到时,与声明关联介绍了它.( http://en.cppreference.com/w/cpp/language/lookup)
  3. 模板参数说明/推论:为了实例化函数模板,每个模板参数必须为已知,但并非必须指定每个模板参数.什么时候可能的话,编译器将推断出缺少的模板参数从函数参数.( http://en.cppreference.com/w/cpp/language/template_argument_deduction)
  4. 模板替换(可能会触发SFINAE):每次使用功能参数列表中的模板参数被替换为相应的模板参数.替换失败(即,无法用推论或提供的内容替换模板参数模板参数)的功能模板可删除该功能过载集合中的模板.( http://en.cppreference.com/w/cpp/language/function_template#Template_argument_substitution )
  5. 形成过载集合:在开始过载解决之前,通过名称查找和模板参数推导选择的函数合并以形成候选函数集.( http://en.cppreference.com/w/cpp/language/overload_resolution#Details )
  6. 重载分辨率:通常,候选函数的参数最匹配参数的是叫.( http://en.cppreference.com/w/cpp/language/overload_resolution)
  7. 模板实例化:必须确定模板参数以便编译器可以从中生成实际的函数(或类类模板).( http://en.cppreference.com/w/cpp/language/function_template#Function_template_instantiation )
  8. 编译器生成代码.

我将这些要点保留为准则和参考,以供以后使用.此外,我将从步骤1-6参考模板评估.如果您在上述列表中发现任何错误,请随时进行更改或发表评论,以便我进行更改.

I'll keep these bullet-points as guidelines and references for later on. Furthermore, I'll refer to template evaluation from steps 1-6. If you find anything wrong in the above list please feel free to change it or comment so that I can make the changes.

在以下示例中:

class A {};

template<typename, typename = void>
struct test
{ test(){std::cout<< "Using None" <<std::endl;} };

template<typename T>
struct test<T, decltype(T::a)>
{ test(){std::cout<< "Using T::a" <<std::endl;} };

int main()
{ test<A> a; }

两个编译器的输出:

Using None

此示例在g ++和clang中都可以很好地进行编译,因为,当编译器完成所有模板的评估过程时,它将仅选择实例化第一个模板,以使其与用于创建对象的模板参数最匹配在main()中.此外,当编译器无法推断出T :: a(SFINAE)时,模板替换过程也会失败.此外,由于自变量不匹配,因此专用化将不会包含在重载集中,也不会被实例化.

This example compiles fine in both g++ and clang, because, when the compiler completes the evaluation process of all the templates, it will only choose to instantiate the first template for being the best match to the template arguments used to create the object in main(). Also, the template substitution process fails when the compiler fails to deduce T::a (SFINAE). Furthermore, due to the argument mismatch, the specialization will not be included in the overload set and will not be instantiated.

我们应该添加第二个模板参数,就像这样:

Should we add the second template argument, like this:

test<A , decltype(A::a)> a;

代码将无法编译,并且两个编译器都会抱怨:

The code will not compile and both compilers would complain of:

error: no member named 'a' in 'A'

但是,在以下示例中,事情开始变得奇怪:

In the following example however, things start becoming weird:

class A { int a; };

template<typename, typename = void>
struct test
{ test(){std::cout<< "Using None" <<std::endl;} };

template<typename T>
struct test<T, decltype(T::a)>
{ test(){std::cout<< "Using T::a" <<std::endl;} };

int main()
{ test<A> a; }

从clang输出:

Using None

g ++的输出:

error: ‘int A::a’ is private

首先,我认为这将是一个不错的警告.但是为什么会出错呢?该模板甚至都不会实例化.考虑到前面的示例,以及指向成员的指针是在编译时已知的常量值的事实,似乎当clang完成模板评估阶段且SFINAE发生在模板替换时,它将准确地实例化第一个模板并忽略专业化.但是,当g ++经过替换过程并查找变量T :: a时,它会发现它是私有成员,并且没有说SFINAE,而是提示出现上述错误.考虑到此错误报告,我认为这就是错误所在: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61806

To begin with, I think that this would have been a nice warning. But why an error? The template won't even get instantiated. Considering the previous example, and the fact that pointers-to-members are constant values known at compile time, it seems, that when clang completes the template evaluation stage, with the SFINAE occuring at template substitution, it accurately instantiates the first template and ignores the specialization. But when g++ goes through the substitution process, and looks for the variable T::a, it sees that it is a private member, and instead of saying SFINAE, it prompts with the error above. I think that this is where the bug is, considering this bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61806

现在,好奇的部分在下一个示例中,该示例使用私有成员函数:

Now, the curious part is in the next example, which uses the private member function:

class A{ void a() {}; };

template<typename, typename = void>
struct test
{ test(){std::cout<< "Using None" <<std::endl;} };

template<typename T>
struct test<T,decltype(&T::a)>
{ test(){std::cout<< "Using A::a" <<std::endl;} };

int main()
{ test<A> a; }

两个编译器的输出:

Using None

如果前面的解释是正确的,那么在使用private成员函数时,为什么g ++不会提示错误?同样,这只是基于输出的假设,但我认为这一位实际上应该按预期工作.长话短说,SFINAE开始工作,从重载集中放弃专业化,只实例化第一个模板.也许它的作用超出了人们的想象,但是,如果我们明确指定第二个模板参数,则两个编译器都会提示相同的错误.

If the previous explanation is true, then why doesn't g++ prompt an error when the private member function is used? Again, this is only an assumption based on the outputs, but I think that this bit actually works as it should. Long story short, SFINAE kicks in, the specialization is discarded from the overload set, and only the first template gets instantiated. Maybe there's more to it than meets the eye, but if we explicitly specify the second template argument, both compilers will prompt the same error.

int main()
{ test<A , decltype(&A::a)> b; }

两个编译器的输出:

error: ‘void A::a()’ is private

无论如何,这是我一直在使用的最终代码.为了演示输出,我将班级设为公开.作为一个有趣的事件,我添加了一个nullptr来直接指向成员函数. decltype((((T *)nullptr)-> f())中的类型为 void ,而在下面的示例中, a c 都由专业化而不是第一个模板调用.原因是因为第二个模板比第一个模板更专业,因此是两者的最佳匹配(用一块石头杀死两只鸟)(模板正式订购规则:

Anyway, this is the final code I've been using. To demonstrate the outputs, I've made the class public. As an interesting event, I've added a nullptr to point to the member function directly. The type from decltype(((T*)nullptr)->f()) is void, and from the example below, a and c are both invoked by the specialization rather than the first template. The reason is because the second template is more specialized than the first and therefore is the best match for both of them (kill two birds with one stone) (Template Formal Ordering Rules : https://stackoverflow.com/a/9993549/2754510). The type from decltype(&T::f) is M4GolfFvvE (possible translation: Men 4 Golf Fear very vicious Elk), which thanks to boost::typeindex::type_id_with_cvr, it is demangled into void (Golf::*)().

#include <iostream>
#include <boost/type_index.hpp>

class Golf
{
    public:
        int v;

        void f()
        {};
};


template<typename T>
using var_t = decltype(T::v);

template<typename T>
using func_t = decltype(&T::f);
//using func_t = decltype(((T*)nullptr)->f()); //void


template<typename, typename = void>
struct test
{
    test(){std::cout<< "Using None" <<std::endl;}
};

template<typename T>
struct test<T,var_t<T> >
{
    test(){std::cout<< "Using Golf::v" <<std::endl;}
};

template<typename T>
struct test<T,func_t<T> >
{
    test(){std::cout<< "Using Golf::f" <<std::endl;}
};


int main()
{
    test<Golf> a;
    test<Golf,var_t<Golf> > b;
    test<Golf,func_t<Golf> > c;

    using boost::typeindex::type_id_with_cvr;
    std::cout<< typeid(func_t<Golf>).name() << " -> " << type_id_with_cvr<func_t<Golf>>().pretty_name() <<std::endl;
}

两个编译器的输出(func_t = decltype(& T :: f)):

Output from both compilers (func_t = decltype(&T::f)):

Using None
Using Golf::v
Using Golf::f
M4GolfFvvE -> void (Golf::*)()

两个编译器的输出(func_t = decltype((((T *)nullptr)-> f())):

Output from both compilers (func_t = decltype(((T*)nullptr)->f())):

Using Golf::f
Using Golf::v
Using Golf::f
v -> void


根据@ Dr.Gut(下面的评论),该错误在gcc 9.2中仍然存在.但是,我发现使用 std :: declval 对此进行了破解",这使其看起来更加奇怪.

According to @Dr.Gut (comment below) this bug continues to exist in gcc 9.2. However I found a "hack" around this using std::declval which makes it look even weirder.

#include <utility>

class A
{
    int a;
};

template<typename, typename = void>
class test
{};

template<typename T>
class test<T,decltype(std::declval<A>().a)>
{};

int main()
{
    test<A> a;
}

在线示例: https://rextester.com/BUFU29474

代码可以在g ++和vc ++中编译并正常运行,但在clang ++中则无法运行.

The code compiles and runs fine in g++ and vc++ but fails in clang++.

这篇关于模板替换和SFINAE中的私有成员访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆