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

时间:2017-07-26 05:56:52

标签: c++ templates c++17

我有以下代码:

template <typename, typename>
struct AAA{};

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

int main() {
    f<int, int>(nullptr);
}

此代码导致编译错误。使用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);

使用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.

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

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

5 个答案:

答案 0 :(得分:26)

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

[temp.arg.explicit/9]

  

模板参数推导可以扩展模板的顺序   对应于模板参数包的参数,即使是   sequence包含显式指定的模板参数。 [实施例:

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

- 结束示例]

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

[temp.arg.explicit/3]

  

可以推导或获取的尾随模板参数   默认模板参数可以从显式列表中省略   模板参数。 一个尾随模板参数包,否则没有   推导出的推断将被推导出一系列模板参数。   ...

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

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

答案 1 :(得分:15)

对于您正在使用typename ...Args,编译器不知道int, int是否是正在使用的所有模板参数,或者通过推断函数参数可以获得更多模板参数。因此,该函数尚未实例化,编译器继续尝试从函数参数中推导出参数包的所有其他可能参数。

换句话说,这有效:

f<int>(new AAA<int, int>);

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

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

template <typename, typename>
struct AAA{};

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

int main() {
    f<int, int>(nullptr);
}

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

答案 2 :(得分:12)

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

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

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

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

(*****f<int, int>)(nullptr); // still OK - does what you want

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

答案 3 :(得分:6)

我想添加另一个调用{}概念

的解决方案
template <typename, typename>
struct AAA{};

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

int main() {
    f<int, int>({});
}

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

答案 4 :(得分:4)

Great answer by @skypjack

您需要帮助编译器推导出函数参数:

AAA<int, int> *a = nullptr;
f<int, int>(a); //works

f<int, int>( (AAA<int, int> *)nullptr );  //even this will work.

从根本上说,nullptr代表一个“无对象”,可以分配给任何指针类型。