为什么不自动确定类模板构造函数参数?

时间:2011-10-27 19:37:56

标签: c++ templates

考虑以下课程:

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
}

c ++中不允许以下内容:

auto p = Pair(10, 10);

为什么不允许这样做?类型可以从构造函数调用中完全确定 我知道有解决方法,如:

template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T1 &First, const T2 &Second)
{
    return Pair<T1, T2>(First, Second);
}

但为什么需要呢?为什么编译器不能像从函数模板那样从参数中确定类型?你可能会说它因为标准不允许它,所以为什么标准不允许呢?

编辑:
对于那些说这是一个例子的人,为什么不允许这样做:

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

我可以使用函数模板重载完成此操作:

template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T1 &First, const T2 &Second)
{
    return Pair<T1, T2>(First, Second);
}
template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T2 &Second, const T1 &First)
{
    return Pair<T1, T2>(First, Second);
}

为什么这允许函数而不是类?

3 个答案:

答案 0 :(得分:10)

这就是Bjarne Stroustrup就此事所说的话:

  

请注意,永远不会推导出类模板参数。原因是   几个构造函数为类提供的灵活性   在许多情况下会使这种推论变得不可能,并且在很多情况下都会模糊不清   更多。

答案 1 :(得分:5)

请考虑以下事项。

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

p应该是Pair<int,double>还是Pair<double,int>

答案 2 :(得分:2)

通常情况下,通常情况下更好。当您必须设计系统时,您必须小心不同功能的交互方式,并且应该选择最简单的解决方案,为用户提供最大的解决方案。典型的问题始于该功能将为用户提供什么该功能需要的成本该功能可能带来哪些问题。 / p>

在这种情况下,提供类型推导有助于您在声明变量或直接调用构造函数时跳过提供类型:

pair p = pair( 10, 20 );

变量的声明可以使用auto完成,因此它不再是一个问题,但它可能是此功能的激励原因。在调用构造函数的情况下,添加的值非常有限,您可以提供命名构造函数,就像标准库中一样:

auto p = make_pair( 10, 20 );

因此,在一天结束时,能够直接调用构造函数的附加值实际上是有限的,因为您始终可以提供将为您创建对象的命名函数。请注意,潜在的额外副本已经过优化,这意味着调用make_pair与直接调用构造函数`对(10,20)无需额外成本

现在,请关注该功能可能对用户代码产生的负面影响。该功能仅在某些情况下可用,其中没有竞争构造函数重载,就像它找到函数模板的最佳重载一样,但更重要的是特殊化。编译器必须查找类模板的所有潜在特化,并确定它们中的每一个提供的构造函数集,然后它必须解析最佳构造函数重载和封闭类模板。

回顾您提供的特定示例,std::pair没有专门化,所以我们处于简单的情况,但上面的示例代码将失败:

pair p = pair( 10, 20 );

为什么呢?嗯,这实际上是问题,这个功能会给用户带来相当多的困惑。正如我所提到的,表达式的右侧可以通过普通的重载决策轻松解决(假设没有特化),并且它将推导出int这两个参数。因此,第一步将表达式推导为:

pair p = pair<int,int>(10,20);

现在,这相当于pair p( pair<int,int>( 10, 20 ) ),我们需要第二步解决,此时,编译器会看到有一个模板化的构造函数:

template <typename first_type, typename second_type>
struct pair {
   first_type first;
   second_type second;

   template <typename U, typename V>
   pair( U f, V s ) : first(f), second(s) {}
};

这意味着std::pair<T,U>的每个潜在实例都与该构造函数调用完全匹配,并且编译器无法选择,也不能提供任何合理的错误消息,而不是模糊不清

现在想象一下,来到互联网上你最喜欢的Q&amp; A论坛,猜猜这类细节会产生多少问题。忽略实现该功能的成本,在语言中使用它的结果是语言会更复杂,更难写,它会带来更陡峭的学习曲线,而这一切基本上没有真正的价值:在这种情况下在可以应用演绎的情况下,编写一个能够提供相同优势的辅助函数是很简单的,例如make_pair 而不增加成本

您必须提供类型这一事实会消除用户的复杂性:无需考虑编译器是否能够为我推断正确的类型。不必专注于这些细节,您就有更多时间思考您想要解决的实际问题。

有时 less 更好

BTW,auto以最微不足道的方式解决了这个问题:它不需要找到一个构造函数来匹配并猜测它的类型,但只需使用它的类型右侧表达式,它更简单:只适用于单个对象,并将该类型用于新对象。即便如此,通过大大简化的用例,在讨论商定的解决方案之前来回讨论,其中的细节是auto的扣除是否可用于创建引用,或者是否constvolatile将是推断类型的一部分...

只是一个想法:不使用编译器,表达式std::min( 10, 5.0 )的类型是什么?