为什么函数参数类型中使用的模板参数包作为其模板参数列表无法显式指定

gna*_*yil 39 c++ templates c++17

我有以下代码:

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>(nullptr);
}
Run Code Online (Sandbox Code Playgroud)

此代码导致编译错误.使用g++ -std=c++1z错误编译时显示如下:

prog.cc: In function 'int main()':
prog.cc:8:24: error: no matching function for call to 'f<int, int>(std::nullptr_t)'
     f<int, int>(nullptr);
                        ^
prog.cc:5:6: note: candidate: template<class ... Args> void f(AAA<Args ...>*)
 void f(AAA<Args...> *) {}
      ^
prog.cc:5:6: note:   template argument deduction/substitution failed:
prog.cc:8:24: note:   mismatched types 'AAA<Args ...>*' and 'std::nullptr_t'
     f<int, int>(nullptr);
Run Code Online (Sandbox Code Playgroud)

使用clang++ -std=c++1z错误是:

prog.cc:8:5: error: no matching function for call to 'f'
    f<int, int>(nullptr);
    ^~~~~~~~~~~
prog.cc:5:6: note: candidate template ignored: could not match 'AAA<int, int, Args...> *' against 'nullptr_t'
void f(AAA<Args...> *) {}
     ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

我在MSYS2 MinGW-w64环境中运行上面的那些.我的GCC版本是GCC 7.1.0,我的Clang版本是4.0.0; 我在GCC和Clang中使用的标准库是与我的GCC编译器捆绑在一起的libstdc ++.

在我看来,对函数模板的调用foo具有显式指定的模板参数,因此应该已经指定了模板参数包和函数参数类型.但是,上面显示的错误诊断似乎表明函数参数和nullptr参数的确切类型不匹配,这似乎只有在函数参数推断发生时才可能出现问题.所以我的问题是,为什么会出现这种错误?它只是一个编译器错误,还是C++标准有一些规则表明原始代码只是格式不正确?

Sto*_*ica 26

您可能认为编译器应该将包推断为int ,int,但C++标准明确要求您观察到的行为.

[temp.arg.explicit/9]

模板参数推导可以扩展与模板参数包对应的模板参数序列,即使序列包含显式指定的模板参数也是如此.[例如:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0);     // Types is deduced to the sequence int*, float*, int
}
Run Code Online (Sandbox Code Playgroud)

- 结束例子]

以上意味着即使指定了一些参数,扣除也不会结束.参数包必须始终可通过参数推导进行扩展.就好像给出的显式参数是具有尾随参数包的模板的实例化.加上以下内容:

[temp.arg.explicit/3]

可以从显式模板参数列表中省略可以从默认模板参数推导或获得的尾随模板参数.未以其他方式推导出的尾随模板参数包将被推导为空的模板参数序列. ...

编译器必须将未推导的参数与空包匹配.但它没有什么可以推断它.

因此,您尝试插入Args...AAA不可能匹配.因为类型序列是两个带有尾随列表的类型(编译器不能推断为空nullptr).虽然AAA只需要两种类型.


sky*_*ack 15

对于您正在使用的情况typename ...Args,编译器不会int, int通过推断函数参数来了解是否所有正在使用的模板参数都可用.因此,该函数尚未实例化,编译器继续尝试从函数参数中推导出参数包的所有其他可能参数.

换句话说,这有效:

f<int>(new AAA<int, int>);
Run Code Online (Sandbox Code Playgroud)

因为你说第一个参数是int,但是编译器需要一个参数列表并继续试图从函数参数中贪婪地找到越来越多的参数,然后它实例化了函数模板.

在您的情况下,或多或少会发生相同的情况,但编译器无法推断出nullptr_t函数参数不匹配的任何内容.它需要一个指向a的指针A<...>,当你传入时不是这种情况nullptr.
这将改为:

template <typename, typename>
struct AAA{};

template<typename A, typename B>
void f(AAA<A, B> *) {}

int main() {
    f<int, int>(nullptr);
}
Run Code Online (Sandbox Code Playgroud)

因为编译器知道模板参数是两个并且您提供所有这些参数,所以没有什么可以推断出来并且可以实例化该函数.它也更有意义,因为AAA只接受两个模板参数,所以参数包f在这里似乎没用.


Bar*_*rry 12

只是添加一个简单的解决方案:

f<int, int>(nullptr); // doesn't work for the reasons explained by other answers
(*f<int, int>)(nullptr); // OK - does what you want
Run Code Online (Sandbox Code Playgroud)

后者迫使包装Args...{int, int},现在的调用本身不是一个函数模板调用-它只是一个函数指针调用.我们正在调用一个函数,AAA<int, int>*当然nullptr可以接受传递.

为了好玩,您还可以任意添加多个*s:

(*****f<int, int>)(nullptr); // still OK - does what you want
Run Code Online (Sandbox Code Playgroud)

......但是,你知道......不要.

  • @MartinBonner你需要`*`(或`&`)来强制扣除.括号用于运算符优先级,因为`*f <int,int>(nullptr)`order喜欢`*(f <int,int>(nullptr))`. (2认同)

Joh*_*itb 6

我想添加另一个调用这个{}概念的解决方案

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>({});
}
Run Code Online (Sandbox Code Playgroud)

当参数{}为时,对参数的推导被禁用(非推导上下文),因此不存在不匹配,并且参数初始化实际上也产生空指针.