类模板的模板参数推导的陷阱

Cur*_*ous 5 c++ templates forwarding forwarding-reference c++17

我正在阅读有关类模板的模板参数推导的论文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&&) { ... }
};
Run Code Online (Sandbox Code Playgroud)

鉴于上面的代码,如果有人尝试像这样实例化上述模板的实例

auto something = Something{std::shared_ptr<int>{}};
Run Code Online (Sandbox Code Playgroud)

右值引用重载总是会被调用吗?由于考虑扣除的过载集是

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&&) { ... }
Run Code Online (Sandbox Code Playgroud)
  1. 第二个重载永远不会优于第一个重载(因为它现在是转发引用重载,而不是右值引用重载),那么这里应该发生什么?
  2. 如果不明确指定参数,似乎永远无法调用最后一个T,这是预期的行为吗?
  3. 在对类模板使用模板参数推导时,是否还应该记住其他任何陷阱或风格指南?
  4. 此外,用户定义的推导指南是否需要位于类定义之后?例如,您可以在类定义本身的类构造函数的声明中包含尾随返回类型吗?(与这里的迭代器构造函数不同http://en.cppreference.com/w/cpp/language/class_template_deduction

Bar*_*rry 2

  1. 第二个重载永远不会优于第一个重载(因为它现在是转发引用重载,而不是右值引用重载),那么这里应该发生什么?

不,这不是转发参考。这是一个关键的区别。来自[temp.deduct.call]

转发引用是对 cv 不合格模板参数的右值引用,该模板参数不表示类模板的模板参数(在类模板参数推导 ([over.match.class.deduct]) 期间)。

您的候选人是:

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
Run Code Online (Sandbox Code Playgroud)

当你写:

auto something = Something{std::shared_ptr<int>{}};
Run Code Online (Sandbox Code Playgroud)

构造函数T&&是首选,带有T=std::shared_ptr<int>,因此您最终会得到Something<std::shared_ptr<int>>类模板专业化。如果改为写:

std::shared_ptr<int> p;
auto something = Something{p};
Run Code Online (Sandbox Code Playgroud)

那么T const&构造函数是首选(实际上它是唯一可行的候选者)。尽管我们最终都在同一个地方:Something<std::shared_ptr<int>>

  1. 如果不明确指定 T 参数,似乎永远无法调用最后一个,这是预期的行为吗?

正确,T是非推导的上下文。这是有道理的 - 这个构造函数的存在是为了进行转换,但是您需要指定要转换的内容才能进行转换。让这种“正常工作”对你来说是没有意义的。

  1. 此外,用户定义的推导指南是否需要位于类定义之后?

是的。按照规则,这就是他们要去的地方。在构造函数中具有尾随返回类型是没有意义的 - 构造函数不会“返回”任何内容。