为什么不能在`std::reference_wrapper`中推导模板实例?

发布于 2024-12-21 01:50:28 字数 896 浏览 5 评论 0 原文

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

int a = 5, b = 7;

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

现在我可以轻松地说 if (p ,因为引用包装器有一个到其包装类型的转换。一切都很愉快,我可以处理引用包装器的集合,就像它们是原始对象一样。

(如下面链接的问题所示,这可能是生成现有集合的备用视图的有用方法,可以随意重新排列,而不会产生完整副本的成本,也可以保持原始集合的更新完整性。)


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

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

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

return t1 < t2;  // ERROR

我的解决方法是定义一个谓词这个答案*;但我的问题是:

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

溺深海 2024-12-28 01:50:28

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

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


§14.8.3 [温度超过] 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 const& 是转换目标)来测试它。由于在这种情况下转换匹配,重载解析成功,我们得到了函数调用。

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.

浅听莫相离 2024-12-28 01:50:28

std::reference_wrapper 没有 operator<,因此执行 ref_wrapper 的唯一方法是通过 ref_wrapper code> 成员:

operator T& () const noexcept;

如您所知,std::string 是:

typedef basic_string<char> string;

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 此函数声明模板通过匹配 string = basic_string 进行实例化,解析为 charT< /code> = char 等。

因为 std::reference_wrapper (或其任何(零)基类)无法匹配basic_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
}

给出

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 限定性代码>A

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

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

请参阅下面的评论。

  • 如果 P 是一个类,并且 P 的形式为 simple-template-id,则转换后的 A > 可以是推导的 A 的派生类。

注释

这意味着在本段中:

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

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

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

std::reference_wrapper does not have an operator<, so the only way to do ref_wrapper<ref_wrapper is via the ref_wrapper member:

operator T& () const noexcept;

As you know, std::string is:

typedef basic_string<char> string;

The relevant declaration for string<string is:

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

For string<string this function declaration template is instantiated by matching string = basic_string<charT,traits,Allocator> which resolves to charT = char, etc.

Because std::reference_wrapper (or any of its (zero) bases classes) cannot match basic_string<charT,traits,Allocator>, the function declaration template cannot be instantiated into a function declaration, and cannot participate in overloading.

What matters here is that there is no non-template operator< (string, string) prototype.

Minimal code showing the problem

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&)'

Standard citations

14.8.2.1 Deducing template arguments from a function call [temp.deduct.call]

Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below.

(...)

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above). However, there are three cases that allow a difference:

  • If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.

Note that this is the case with std::string()<std::string().

  • The transformed A can be another pointer or pointer to member type that can be converted to the deduced A via a qualification conversion (4.4).

See comment below.

  • If P is a class and P has the form simple-template-id, then the transformed A can be a derived class of the deduced A.

Comment

This implies that in this paragraph:

14.8.1 Explicit template argument specification [temp.arg.explicit]/6

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.

the if should not be taken as a if and only if, as it would directly contradict the text quoted previously.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文