为什么模板实例不能在`std :: reference_wrapper`中推导出来? [英] Why can template instances not be deduced in `std::reference_wrapper`s?

查看:214
本文介绍了为什么模板实例不能在`std :: reference_wrapper`中推导出来?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  int a = 5,b = 7; 

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

code> if(p< q),因为引用包装器具有到其包装类型的转换。一切都很开心,我可以像处理原始对象一样处理引用封装器的集合。



(作为下面链接的查询显示,这可以是一个有用的方法来生成现有集合的替代视图,可以随意重新排列,而不会产生一个完整的复制,以及维持与原始集合的更新完整性。)






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

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

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

return t1< t2; //错误



我的解决方法是定义一个谓词,如此回答 *;但我的问题是:



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



*)更新:根据答案,似乎使用 std :: less< T>()是一个通用的解决方案。

解决方案

编辑:将我的猜测移动到底部,这里是规范性文本,为什么这不会工作。 TL; DR版本:


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


< blockquote>




§14.8.3[temp.over] p1



<...>当调用该名称时(显式地或隐式地使用运算符
表示法),模板参数扣除(14.8.2),并且对每个函数模板执行任何显式模板参数(14.3)的检查,以找到可以与该函数模板一起使用的模板参数值(如果有的话)来实例化可以被调用的函数模板特化调用参数。


§14.8.2.1[temp.deduct.call] p4


[...] [注意:如果参数不包含参与模板参数扣除的模板参数,则会对函数参数执行转换以将其转换为相应函数参数的类型 >。 [...] ]


temp.arg.explicit] p6


隐式转换(第4条)如果参数类型不包含参与模板参数扣除的 template-parameters ,则将其转换为相应函数参数的类型。 [注意:如果明确指定模板参数,模板参数不会参与模板参数扣除。 [...] -end note ]


std :: basic_string 取决于推导的模板参数( CharT Traits ),不允许转换。 / p>




这是一个鸡蛋问题。要推导模板参数,它需要一个实际的实例 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;
}

//注释掉这个以获得扣除失败
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< int> const& 作为转换目标)进行测试。由于转换匹配在这种情况下,重载解决成功,我们得到我们的函数调用。


Suppose I have some object of type T, and I want to put it into a reference wrapper:

int a = 5, b = 7;

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

Now I can readily say if (p < q), because the reference wrapper has a conversion to its wrapped type. All is happy, and I can process a collection of reference wrappers just like they were the original objects.

(As the question linked below shows, this can be a useful way to produce an alternate view of an existing collection, which can be rearranged at will without incurring the cost of a full copy, as well as maintaining update integrity with the original collection.)


However, with some classes this doesn't work:

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

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

return t1 < t2;  // ERROR

My workaround is to define a predicate as in this answer*; but my question is:

Why and when can operators be applied to reference wrappers and transparently use the operators of the wrapped types? Why does it fail for std::string? What has it got to do with the fact that std::string is a template instance?

*) Update: In the light of the answers, it seems that using std::less<T>() is a general solution.

解决方案

Edit: Moved my guesswork to the bottom, here comes the normative text why this won't work. TL;DR version:

No conversions allowed if the function parameter contains a deduced template parameter.


§14.8.3 [temp.over] p1

[...] When a call to that name is written (explicitly, or implicitly using the operator notation), template argument deduction (14.8.2) and checking of any explicit template arguments (14.3) are performed for each function template to find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments.

§14.8.2.1 [temp.deduct.call] p4

[...] [ Note: as specified in 14.8.1, implicit conversions will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter contains no template-parameters that participate in template argument deduction. [...] —end note ]

§14.8.1 [temp.arg.explicit] p6

Implicit conversions (Clause 4) will be performed on a function argument to convert it to the type of the corresponding function parameter if the parameter type contains no template-parameters that participate in template argument deduction. [ Note: Template parameters do not participate in template argument deduction if they are explicitly specified. [...] —end note ]

Since std::basic_string depends on deduced template parameters (CharT, Traits), no conversions are allowed.


This is kind of a chicken and egg problem. To deduce the template argument, it needs an actual instance of std::basic_string. To convert to the wrapped type, a conversion target is needed. That target has to be an actual type, which a class template is not. The compiler would have to test all possible instantiations of std::basic_string against the conversion operator or something like that, which is impossible.

Suppose the following minimal testcase:

#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;
}

If we don't provide the overload for an instantiation on int, the deduction fails. If we provide that overload, it's something the compiler can test against with the one allowed user-defined conversion (foo<int> const& being the conversion target). Since the conversion matches in this case, overload resolution succeeds and we got our function call.

这篇关于为什么模板实例不能在`std :: reference_wrapper`中推导出来?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆