类模板的模板参数推导注意事项

5

我正在阅读有关类模板模板参数推导的论文,链接地址 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r3.html。这个功能已经在C++17标准中,但是其中有些东西让我感到困惑。

template <typename T>
class Something {
public:

    // delete the copy and move constructors for simplicity
    Something(const Something&) = delete;
    Something(Something&&) = delete;

    explicit Something(T&&) { ... }
    explicit Something(const T&) { ... }

    template <typename U, typename EnableIfNotT<U, T>* = nullptr>
    Something(U&&) { ... }
};

鉴于上述代码,如果有人尝试像这样实例化上述模板的一个实例:
auto something = Something{std::shared_ptr<int>{}};
右值引用重载总是会被调用吗?由于用于推导的重载集合是
template <typename T>
Something<T> F(T&&) { ... }
template <typename T>
Something<T> F(const T&) { ... }
template <typename T, typename U, typename EnableIfNotT<U, T>*>
Something<T> F(U&&) { ... }
  1. 第二个重载将永远不会优先于第一个重载被调用(因为它现在是一个转发引用重载,而不是右值引用重载),那么这里应该发生什么?
  2. 而且似乎最后一个重载如果没有明确指定 T 参数就无法被调用,这是预期的行为吗?
  3. 此外,在使用类模板的模板参数推断时,是否还有其他需要注意的地方或样式准则?
  4. 另外,用户定义的推导指南是否必须在类定义之后?例如,可以在类定义本身中的构造函数声明中使用尾返回类型(与迭代器构造函数不同,请参见http://en.cppreference.com/w/cpp/language/class_template_deduction)?

(1) 第二个重载实际上与第三个重载相同,即使您传递一个左值,它也会选择第三个重载。 (2) 不,只需将一个左值传递给构造函数即可调用第三个重载。 (3) 时间会证明一切。到目前为止,我猜只有少数人在使用它。 (4) 看起来是这样,但我不认为有理由这样做。也许将来会改变。 - Henri Menke
此外,在您的示例中,什么都无法工作,因为所有构造函数都是私有的,并且缺少“EnableIfNotT”的定义(我猜它是std::enable_if_t<!std::is_same<U,T>::value>)。 - Henri Menke
1
你不应该阅读的是这个功能的论文。花了两张额外的纸来修补说明书。 - T.C.
@T.C. 你能给我提供这个功能的最新描述链接吗?cppreference页面是最好的查找地方吗? - Curious
好的,如果你喜欢工作论文的话,那是最好的选择。但如果你不喜欢的话,cppreference页面看起来相当完整,我没有发现任何明显的问题。 - T.C.
显示剩余6条评论
1个回答

2
第二个重载不会优先于第一个重载(因为现在它是一个转发引用重载,而不是一个右值引用重载),那么这里应该发生什么?
不,这不是一个转发引用。这是一个关键区别。来自[temp.deduct.call]
转发引用是一个到未限定模板参数的右值引用,该模板参数不表示类模板的模板参数(在类模板参数推断期间)。
你的候选项是:
template <typename T>
Something<T> F(T&&);       // this ONLY matches non-const rvalues

template <typename T>
Something<T> F(const T&);  // this matches everything

template <typename T, typename U, typename EnableIfNotT<U, T>*>
Something<T> F(U&&);       // this matches nothing

当你写下以下内容时:

auto something = Something{std::shared_ptr<int>{}};

“T&&”构造函数是首选的,其中“T=std::shared_ptr<int>”,因此您最终会得到“Something<std::shared_ptr<int>>”作为您的类模板特化。如果您相反写成:
std::shared_ptr<int> p;
auto something = Something{p};

那么,T const& 构造函数被优先选择(实际上是唯一可行的候选者)。虽然我们最终得到了相同的结果:Something<std::shared_ptr<int>>

  1. 看起来似乎最后一个构造函数永远不能在不显式指定 T 参数的情况下调用,这是预期的行为吗?

是的,T 是一个未推断的上下文。这是有道理的 - 这个构造函数存在于进行转换时,但你需要指定要进行转换的目标才能进行转换。让它“自动工作”对你来说永远没有意义。

  1. 进一步地,用户定义的模板参数推导向导是否必须放在类定义之后?

是的。根据规则,它们只能放在那里。在构造函数中使用尾随返回类型是没有意义的 - 构造函数不会“返回”任何东西。


谢谢您的回答!因此,由于这个更改,转发引用的规则已经更新了吗?另外,从您提供的链接中还有一件事让我感到困惑。在链接文本块中struct A的定义中,#3中的T&&如何成为一个转发引用?那里的T是一个类模板参数,对吧(因此不应该是转发引用)? - Curious
@Curious 这不是一个类模板参数,而是一个推导指南模板参数。 - Barry
当你编写一个推导指南时,原本不是转发引用的内容现在变成了转发引用?我觉得这很反直觉,你觉得呢?你有什么见解可以解释为什么会这样吗? - Curious
扣除指南不是说模板参数 T 现在是一个转发引用而不是右值引用吗?至少这是我从你提供的链接中的示例推断出来的。 - Curious
@Curious 我甚至不明白那个问题是什么意思。这里有一个更好的例子U&&是一个转发引用,T&&不是一个转发引用 - 它是对T的右值引用。 - Barry
显示剩余13条评论

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