【问题标题】:In function call, why doesn't nullptr match a pointer to a template object?在函数调用中,为什么 nullptr 不匹配指向模板对象的指针?
【发布时间】:2013-12-22 17:20:10
【问题描述】:

这是一个完美运行的代码示例:


#include<iostream>
#include<vector>

template< class D, template< class D, class A > class C, class A = std::allocator< D > >
void foo( C< D, A > *bar, C< D, A > *bas ) {
  std::cout << "Ok!" << std::endl;
}

int main( ) {
  std::vector< int > *sample1 = nullptr;
  std::vector< int > *sample2 = nullptr;
  foo( sample1, sample2 );
  return( 0 );
}

然而,在下面的代码中,编译器无法将第二个参数的 std::vector* 与 nullptr 匹配,甚至无法从第一个参数中扣除模板类型。


#include<iostream>
#include<vector>

template< class D, template< class D, class A > class C, class A = std::allocator< D > >
void foo( C< D, A > *bar, C< D, A > *bas ) {
  std::cout << "Ok!" << std::endl;
}

int main( ) {
  std::vector< int > *sample = nullptr;
  foo( sample, nullptr );
  return( 0 );
}

错误信息是:


$ g++ -std=c++11 nullptr.cpp -o nullptr

nullptr.cpp: In function ‘int main()’:

nullptr.cpp:11:24: error: no matching function for call to ‘foo(std::vector<int>*&, std::nullptr_t)’

   foo( sample, nullptr );

nullptr.cpp:11:24: note: candidate is:

nullptr.cpp:5:6: note: template<class D, template<class D, class A> class C, class A> void foo(C<D, A>*, C<D, A>*)

 void foo( C< D, A > *bar, C< D, A > *bas ) {

nullptr.cpp:5:6: note:   template argument deduction/substitution failed:

nullptr.cpp:11:24: note:   mismatched types ‘C<D, A>*’ and ‘std::nullptr_t’

   foo( sample, nullptr );

为什么会这样?

【问题讨论】:

  • 两件事:1)您想删除“class D, class A”中的名称——这些名称与 D 和 A 冲突一个范围(在 foo 本身的模板参数中)。 2) 错误信息在 monotype 中会更易读。

标签: c++ c++11 function-call template-matching nullptr


【解决方案1】:

这就是模板推导的工作原理:不会发生转换。

这个问题也不是 nullptr 特有的问题,考虑一个极其简单的情况:

#include <iostream>

struct Thing {
    operator int() const { return 0; }
} thingy;

template <typename T>
void print(T l, T r) { std::cout << l << " " << r << "\n"; }

int main() {
    int i = 0;
    print(i, thingy);
    return 0;
}

yields:

prog.cpp: In function ‘int main()’:
prog.cpp:12:17: error: no matching function for call to ‘print(int&, Thing&)’
  print(i, thingy);
                 ^
prog.cpp:12:17: note: candidate is:
prog.cpp:8:6: note: template<class T> void print(T, T)
 void print(T l, T r) { std::cout << l << " " << r << "\n"; }
      ^
prog.cpp:8:6: note:   template argument deduction/substitution failed:
prog.cpp:12:17: note:   deduced conflicting types for parameter ‘T’ (‘int’ and ‘Thing’)
  print(i, thingy);
                 ^

因此,nullptrint* 的转换也不会发生在模板参数推导之前。如前所述,您有两种解决问题的方法:

  • 指定模板参数(因此不会发生扣除)
  • 自己转换参数(推理发生,但在您显式转换之后)

【讨论】:

    【解决方案2】:

    来自 C++ 标准(4.10 指针转换 [conv.ptr])

    1 空指针常量是整数类型的整型常量表达式 (5.19) prvalue 求值为 0 或 std::nullptr_t 类型的纯右值。空指针常量可以是 转换为指针类型;结果是该类型的空指针值,并且是 可与对象指针或函数指针类型的所有其他值区分开来。这样一个 转换称为空指针转换。

    在您的第一个示例中,您的两个 nullptr 在模板参数推导之前已经被转换。所以你有两次相同的类型是没有问题的。

    在第二个中,std::vector&lt;int&gt;std::nullptr_t 不匹配。您必须自己进行转换:static_cast&lt;std::vector&lt;int&gt;*&gt;(nullptr)

    【讨论】:

    • 它必须投射的原因是什么?这个答案与其他答案相同。没有解释为什么
    • @BЈовић 14.8.2.1 Deducing template arguments from a function call 值得一读。基本上模板匹配是严格的并且不寻找转换/提升(除了一些非常特殊的),而函数调用(发生在模板匹配之后)允许转换。这就是为什么this failsthis succeed
    【解决方案3】:

    编译器无法推断出第二个参数类型,因为std::nullptr_t 不是指针类型。

    1 指针字面量是关键字 nullptr。它是一个 std::nullptr_t 类型的纯右值。 [注意:std::nullptr_t 是一个不同的 既不是指针类型也不是指向成员类型的指针的类型; 相反,这种类型的纯右值是一个空指针常量,可以 转换为空指针值或空成员指针值。 [§2.14.7]

    【讨论】:

    • 您说:“编译器无法推断出第二个参数类型...”,但是我们可以说直观上可以从第一个类型推断出 CDA参数(它是显式的),然后可能会发生转换。因此,我认为这里的问题与nullptr_t无关,而与模板推导机制有关。
    【解决方案4】:

    模板参数推导是模式匹配。除了转换为基础之外,它不会对参数进行太多转换(嗯,在类型和decay 上添加const 和引用限定符)。

    虽然nullptr 可以转换为C&lt; D, A &gt;*,但它不是这样的类型。并且两个论点都平等地参与了推论。

    您可以使用typename std::identity&lt;C&lt; D, A &gt; &gt;::type* 之类的东西来阻止对第二个参数的推导,第一个参数也是如此。如果对两个参数都这样做,则不会推导出 template 类型。

    另一种方法是取两种任意类型,然后使用SFINAE确保一种类型的指针可以转换为另一种类型,并且可以从另一种类型转换为的一种可以推导出为@ 987654327@ 用于某些 template C 和类型 DA。这可能与您对函数类型推导应该做什么的内部心理模型相匹配。但是,结果会非常非常冗长。

    更好的方法可能是询问“您希望对这两个参数做什么”,然后对其进行鸭式测试,而不是进行类型匹配。

    【讨论】:

      【解决方案5】:

      这是为了防止您创建一个以 nullptr 作为参数的模板。 你很可能不希望那样。您希望模板使用 propper 类作为参数,并将 nullptr 作为该参数的值。

      你可以

      • 显式调用正确版本的模板
      • 将 nullptr 转换为模板的属性类型。
      • 创建一个正确指针类型的本地变量,为其赋值 nullptr 并使用该变量进行调用。

      【讨论】:

      • std::nullptr 是纯右值,可转换为任何指针类型。编译器应该能够推导出 A 和 C,并将 nullptr 转换为适当的类型......还是我错了?
      • @BЈовић 你是,因为类型 C 将是 nullptr 类型。只要模板使用正确的类型(如vector*),它就会自动转换。在这种情况下,它还必须从 nullptr 推断类型。想想make_pair(nullptr, nullptr) 的结果是什么。如果您执行 `make_pair(nullptr, nullptr),它将起作用。
      • 我没有-1,但我认为这个和其他答案是错误的。我认为它因其他原因而失败。除了你和其他回答者只是从问题中说出了问题——你并没有真正说出原因
      • @BЈовић 看看它是否可行,你最终会得到像 vector&lt;nullptr_type&gt;pair&lt;nullptr_type, nullptry_type&gt; 这样的模板,因为它们只能存储 nullptr。您希望模板参数是某个类,以便您可以将其作为pair&lt;A*, int*&gt; 传递。在大多数情况下,这将是一个错误。我不确定它是如何实现的,或者编译器之间的实现方式是否不同,但这不是问题。
      • 抱歉,这不是真的。谁会需要vector&lt;nullptr_t&gt; 这样的东西?无论如何,here 是正确而详细的答案。
      猜你喜欢
      • 1970-01-01
      • 2019-01-04
      • 1970-01-01
      • 1970-01-01
      • 2019-10-28
      • 2023-03-28
      • 1970-01-01
      • 1970-01-01
      • 2015-04-26
      相关资源
      最近更新 更多