【问题标题】:Why can template instances not be deduced in `std::reference_wrapper`s?为什么 `std::reference_wrapper`s 中不能推导出模板实例?
【发布时间】:2012-01-20 17:36:03
【问题描述】:

假设我有一些T 类型的对象,我想将它放入引用包装器中:

int a = 5, b = 7;

std::reference_wrapper<int> p(a), q(b);   // or "auto p = std::ref(a)"

现在我可以很容易地说if (p &lt; q),因为引用包装器已转换为其包装类型。一切都很好,我可以像处理原始对象一样处理引用包装器的集合。

(正如question linked below 所示,这可能是生成现有集合的替代视图的有用方法,可以随意重新排列,而不会产生完整副本的成本,以及维护更新与原始集合的完整性。)


但是,对于某些类,这不起作用:

std::string s1 = "hello", s2 = "world";

std::reference_wrapper<std::string> t1(s1), t2(s2);

return t1 < t2;  // ERROR

我的解决方法是定义一个谓词this answer*;但我的问题是:

为什么以及何时可以将运算符应用于引用包装器并透明地使用包装类型的运算符?为什么std::string 会失败?与std::string 是模板实例这一事实有什么关系?

*) 更新:根据答案,使用std::less&lt;T&gt;() 似乎是一种通用解决方案。

【问题讨论】:

  • 我怀疑这与所讨论的运算符是一个免费的模板函数有关。由于输入当前是 reference_wrapper > 而不是 basic_string 的一些特化,因此不考虑模板函数并且不能用于导致隐式转换。不过我的标准符比较弱,留给比较熟悉的人来回答吧。
  • @DaveS:我认为你在正确的轨道上。对此效果的详尽回答将不胜感激。

标签: c++ templates implicit-conversion template-argument-deduction reference-wrapper


【解决方案1】:

编辑: 将我的猜测移至底部,这是为什么这不起作用的规范文本。 TL;DR 版本:

如果函数参数包含推导的模板参数,则不允许转换。


§14.8.3 [temp.over] p1

[...] 写入对该名称的调用时(显式或隐式使用运算符 符号),模板参数推导(14.8.2)和任何显式模板参数的检查(14.3)为每个函数模板执行以找到可用于该函数模板以实例化函数模板的模板参数值(如果有)可以通过调用参数调用的特化。

§14.8.2.1 [temp.deduct.call] p4

[...] [注意: 如 14.8.1 中所述,将对函数参数执行隐式转换,以将其转换为对应的类型函数参数如果参数不包含参与模板参数推导的模板参数。 [...] ——尾注 ]

§14.8.1 [temp.arg.explicit] p6

如果参数类型不包含参与模板参数推导的模板参数,将对函数参数执行隐式转换(第 4 条)以将其转换为相应函数参数的类型。 [ 注意: 如果显式指定了模板参数,则它们不参与模板参数推导。 [...] ——尾注 ]

由于std::basic_string 依赖于推导出的模板参数(CharTTraits),因此不允许转换。


这是一个先有鸡还是先有蛋的问题。要推导出模板参数,它需要std::basic_string 的实际实例。要转换为包装类型,需要一个转换目标。该目标必须是实际类型,而类模板不是。编译器必须针对转换运算符或类似的东西测试std::basic_string 的所有可能实例化,这是不可能的。

假设以下最小测试用例:

#include <functional>

template<class T>
struct foo{
    int value;
};

template<class T>
bool operator<(foo<T> const& lhs, foo<T> const& rhs){
    return lhs.value < rhs.value;
}

// comment this out to get a deduction failure
bool operator<(foo<int> const& lhs, foo<int> const& rhs){
    return lhs.value < rhs.value;
}

int main(){
    foo<int> f1 = { 1 }, f2 = { 2 };
    auto ref1 = std::ref(f1), ref2 = std::ref(f2);
    ref1 < ref2;
}

如果我们不为 int 上的实例化提供重载,则推导失败。如果我们提供该重载,则编译器可以使用允许的用户定义转换(foo&lt;int&gt; const&amp; 是转换目标)对其进行测试。由于在这种情况下转换匹配,重载解析成功,我们得到了函数调用。

【讨论】:

  • 演绎失败的例子或反例(以及这与string 是一个不同于int 的模板实例的关系)同样有用! :-)
  • @Kerrek:添加了一个小例子,仍在寻找可以从标准中引用的内容(我只是不高兴在这类问题上没有一个)。
  • @Kerrek:那里,得到标准报价。 :)
  • "[ 注意:如 14.8.1 中所述,如果参数不包含参与的模板参数,则会对函数参数执行隐式转换以将其转换为相应函数参数的类型模板参数推导。[...] — 尾注 ]" 很有趣,但这只是信息性文本。 (如果没有规范性文本支持,则无效。)
  • @curiousguy:它备份,这是我回答中下面的引述。
【解决方案2】:

std::reference_wrapper 没有operator&lt;,所以ref_wrapper&lt;ref_wrapper 的唯一方法是通过ref_wrapper 成员:

operator T& () const noexcept;

如你所知,std::string 是:

typedef basic_string<char> string;

string&lt;string的相关声明为:

template<class charT, class traits, class Allocator>
bool operator< (const basic_string<charT,traits,Allocator>& lhs, 
                const basic_string<charT,traits,Allocator>& rhs) noexcept;

对于string&lt;string,此函数声明模板通过匹配string = basic_string&lt;charT,traits,Allocator&gt; 实例化,解析为charT = char 等。

因为std::reference_wrapper(或其任何(零)基类)不能匹配basic_string&lt;charT,traits,Allocator&gt;,所以函数声明模板不能实例化为函数声明,也不能参与重载。

这里重要的是没有非模板operator&lt; (string, string)原型。

显示问题的最少代码

template <typename T>
class Parametrized {};

template <typename T>
void f (Parametrized<T>);

Parametrized<int> p_i;

class Convertible {
public:
    operator Parametrized<int> ();
};

Convertible c;

int main() {
    f (p_i); // deduce template parameter (T = int)
    f (c);   // error: cannot instantiate template
}

Gives:

In function 'int main()':
Line 18: error: no matching function for call to 'f(Convertible&)'

标准引用

14.8.2.1 从函数调用中推导出模板参数 [temp.deduct.call]

模板参数推导是通过将每个函数模板参数类型(称为P)与调用的相应参数类型(称为A)进行比较来完成的,如下所述。

(...)

一般来说,推导过程会尝试找到模板参数值,使推导的AA 相同(在A 类型按上述转换后)。但是,有三种情况允许区别:

  • 如果原始 P 是引用类型,则推导的 A(即引用所引用的类型)可以比转换后的 A 更具 cv 限定性。

请注意,std::string()&lt;std::string() 就是这种情况。

  • 转换后的A 可以是另一个指针或指向成员类型的指针,可以通过限定转换 (4.4) 转换为推导的 A

请参阅下面的评论。

  • 如果P 是一个类并且P 具有simple-template-id 形式,那么转换后的A 可以是推导出的A 的派生类。李>

评论

这意味着在本段中:

14.8.1 显式模板参数规范 [temp.arg.explicit]/6

将在函数参数上执行隐式转换(第 4 条)以将其转换为相应函数参数的类型 如果参数类型不包含参与模板参数推导的模板参数。

if 不应被视为 当且仅当,因为它会直接与之前引用的文本相矛盾。

【讨论】:

    猜你喜欢
    • 2012-02-21
    • 2018-12-09
    • 1970-01-01
    • 2018-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    相关资源
    最近更新 更多