部分模板特化触发static_asserts

use*_*989 13 c++ c++11

考虑这段代码

template <typename T>
struct delay : std::false_type{};

template <typename T>
struct my_typelist {
    static_assert(delay<T>{}, "");
};

template <typename Tuple>
struct test;

template <typename T>
struct test<my_typelist<T>> {
    void pass(){}
};

template <typename T>
void fail(const test<T> &){}

int main()
{
    test<my_typelist<int>> t;
    t.pass();
    fail(t);
}
Run Code Online (Sandbox Code Playgroud)

没有调用fail()代码编译并运行正常.但是,t在任何函数中使用似乎都会static_assertmy_typelist类中触发,即使该类从未实例化.尽管这个例子是人为的,std::tuple但是我在一个内部使用了不完整的类型遇到了同样的问题,尽管我只是用作std::tuple一个类型列表并且从未实例化它.

为什么static_assert只有在我将变量用作参数时才触发,而不是在我调用成员函数时?在什么情况下my_typelist实例化,什么时候不实例?

请注意,我会使用可变参数模板,但无论如何都会发生错误,因此我选择使用最小公分母.

cpp*_*ner 9

fail(t)if 的情况下,如果my_typelist<int>声明fail在其类定义中命名的友元函数,那么该函数将通过依赖参数的查找来找到(因为它my_typelist<int>是一个关联的类test<my_typelist<int>>).在某些情况下,fail可以通过重载解析(演示)来选择该朋友而不是全局函数.因此,my_typelist<int>必须实例化并检查定义,以确定是否会发生这种情况.添加括号fail将抑制依赖于参数的查找并删除实例化的需要my_typelist<int>,并且在这种情况下,static_assert不会触发(演示).

在没有实例化的情况下t.pass(),my_typelist<int>因为已知t.pass()它将始终调用成员函数test<my_typelist<int>>,并且不会受到完整性的影响my_typelist<int>.


Whi*_*TiM 7

所以,让我们走这条路:

首先static_assert从这个工作代码改变你:

template <typename T>
struct my_typelist {
    static_assert(delay<T>{}, "");
};
Run Code Online (Sandbox Code Playgroud)

对此:

template <typename T>
struct my_typelist {
    static_assert(false, "");
};
Run Code Online (Sandbox Code Playgroud)

它会立即开火.请注意,false表达式不依赖于任何类型.


现在,它变成一个delay<T>不依赖于任何模板参数类型,比如char,int等:

template <typename T>
struct my_typelist {
    static_assert(delay<int>{}, "");
};
Run Code Online (Sandbox Code Playgroud)

它仍然会立即开火.


那么这里发生了什么?

类模板即使在使用时也不会被隐式实例化,除非它在需要完全定义类型的上下文中使用.

temp.inst/1

除非已明确实例化类模板特化([temp.explicit])或显式专用([temp.expl.spec]),否则在需要完全定义的上下文中引用特化时,将隐式实例化类模板特化.对象类型或类类型的完整性影响程序的语义.

请参阅@ cpplearner的答案,了解调用函数fail(...)实例化的原因my_typelist<int>.基本上ADL会强制执行此类实例化,您可以使用限定名称::foo或括号来抑制.

为了完整性:在ADL的其中一个规则中(强调我的):

basic.lookup.argdep/2:对于函数调用中的每个参数类型T,都有一组零个或多个关联的命名空间以及一组零个或多个要考虑的关联类.

  • ....

  • basic.lookup.argdep/2.2:如果T是一个类类型(包括联合),它的关联类是:类本身; 它所属的成员,如果有的话; 及其直接和间接基类.其关联的命名空间是其关联类的最内部封闭命名空间.此外,如果T是类模板特化,则其关联的名称空间和类还包括:与模板类型参数(模板模板参数除外)提供的模板参数类型相关联的名称空间和类; 任何模板模板参数都是成员的名称空间; 以及用作模板模板参数的任何成员模板的类都是成员.[注意:非类型模板参数不会对关联的命名空间集合产生影响. - 结束说明]

  • ....

  • 那么,为什么`t.pass();`不要求`my_typelist <int>`是一个完全定义的类型,为什么`fail(t);`确实要求`my_typelist <int>`是一个完整的类型? (3认同)