当使用花括号初始化列表时,模板参数推导失败。

3

我正在尝试在“perpendicular()”函数中使用模板参数推导:

#include <iostream>

template <typename component = double>
struct offset {
  component x;
  component y;
};

template <typename component>
offset(component x, component y) -> offset<component>;

template <typename component>
offset<component> perpendicular(offset<component> const &o) {
  return offset{o.y, -o.x};
}

template <typename component>
std::ostream &operator<<(std::ostream &s, offset<component> const &o) {
  return s << '(' << o.x << ", " << o.y << ')';
}

int main() {
  std::cout << perpendicular({3.1, 1.2}) << '\n';
  return 0;
}

然而,这段代码无法编译;Clang(使用-std='c++17')会提示:candidate template ignored: couldn't infer template argument 'component' offset<component> perpendicular(offset<component> const &o) {

我应该写成perpendicular(offset{1.0, 2.0})还是有其他方法可以给编译器一个提示呢?

3个回答

3

{/*..*/}的问题在于它没有类型,大多数情况下只能被推断为std::initializer_list<T>T[N]

因此,以下内容将允许所需的语法:

template <typename component>
offset<component> perpendicular(component const (&o)[2]) {
    return offset{o[1], -o[0]};
    // return perpendicular(offset{o[0], o[1]});
}

Demo


我的理解是 { ... } 用于初始化 offset,然后将其传递给 perpendicular,即 offset x = {3.0, 2.1}; perpendicular(x)(这个方法可行)。我还发现在模板参数中添加默认值 (template <typename component = double>) 可以使其编译通过。 - user11589013
使用template <typename T = double> offset<T> perpendicular(offset<T>),在对perpendicular({42, 51})进行推导时,由于不会发生推导,您将获得double而不是期望的int - Jarod42
当我调用 perpendicular({3.12, 1.34}) 时,编译器是不是会找一个 perpendicular(initializer_list<double>) 而不是 perpendicular(offset{initializer_list<double>})? 即使没有其他的重载函数,我仍然不确定为什么会发生这种情况。 - user11589013
2
事实上,通常是反过来做的:名称查找发现template <typename T> perpendicular(offset<T>),而您没有提供offset,因此无法推断出T(它不会尝试offset{args})。 - Jarod42

0

Jarod42的回答给出了你想要的语法,但我主观地认为这不是理想的。你最初想要传递一个偏移量,但现在你正在传递一个数组并将其转换为偏移量。这是一种奇怪的类型关系。

不要使用结构体和单独的函数,只需将它们全部放入Offset类中即可。这实际上并不需要额外的工作,并且可以更好地编写C++代码。你所拥有的更类似于面向对象的C。

#include <iostream>

// Create a self-contained class
template <typename Component = double>
class Offset {
 public:
  Offset(Component x, Component y) : x(x), y(y) {}

  // No longer requires function parameters
  Offset const perpendicular() const { return Offset(y, -x); }

  // I appreciate your use of east const
  friend std::ostream& operator<<(std::ostream& sout,
                                  Offset<Component> const& o) {
    return sout << '(' << o.x << ", " << o.y << ')';
  }

 private:
  Component x;
  Component y;
};

int main() {
  // Subjectively much cleaner to read and understand
  std::cout << Offset{3.1, 1.2}.perpendicular() << '\n';
  return 0;
}

为了以后的参考,您可以在C++14中使用decltype(auto)作为返回类型,完全放弃尾随返回类型语法。


1
template <typename component> offset(component x, component y) -> offset<component>; 不是一个函数,而是一个推导指南。 - user11589013
我确实说过这种方式在主观上更好。我还说过,我认为 OP 目前的解决方案 perpendicular(Offset{3.1, 1.2}) 比更改垂直函数要好。我也喜欢封装,只要它有意义,在这里我认为是有意义的。除非你有/想要一个偏移量,否则你不会真正拥有垂直线,所以把它们捆绑在一起。SO 的整个目的就是让 OP 获得这些不同的观点并做出决定。希望能学到新东西。 - sweenish
你可能会阅读非成员函数如何改善封装性(不是原始链接,但它似乎已经失效了 :-/)。 - Jarod42
从链接中:“如果添加超出真正必要的功能可以显着改善类的性能,使类更易于使用或防止可能的客户端错误,则可以证明这是合理的。”我认为perpendicular()作为类成员函数可以满足第二点和第三点。但再次强调,这仍然是主观的。 - sweenish
从C++14开始,autodecltype(auto)都可以不使用尾随返回类型。auto更安全,因为它永远不会推导出引用。在需要保留返回类型中的引用的罕见情况下,需要使用decltype(auto)。你误解了《Effective Modern C++》(我也读过这本书)。 - Eugene
显示剩余8条评论

0

一种选择是向 perpendicular 添加一个接受两个值的重载。

template <typename component>
offset<component> perpendicular(component v1, component v2) {
    return {v2, -v1};
}

这也可以通过参数包更加通用化,可能与std::common_type结合使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接