在新的初始化序列存在的情况下,运算符重载决议如何工作?
Posted
技术标签:
【中文标题】在新的初始化序列存在的情况下,运算符重载决议如何工作?【英文标题】:How does operator overload resolution work in the presence of the new initializer sequence? 【发布时间】:2014-02-17 18:05:41 【问题描述】:给定以下代码:
#include <iostream>
#include <vector>
template <typename Source>
class ConvertProxy
Source const* m_source;
public:
ConvertProxy( Source const& source )
: m_source( &source )
template <typename Dest>
operator Dest() const
return Dest(m_source->begin(), m_source->end());
;
template <typename Source>
ConvertProxy<Source> convert( Source const& source )
return ConvertProxy<Source>( source );
int
main()
std::vector<int> src;
for ( int i = 0; i != 5; ++ i )
src.push_back( i );
std::vector<double> dest = convert( src ); /* XXX */
for ( std::vector<double>::const_iterator i = dest.begin(), e = dest.end();
i != e;
++ i )
std::cout << *i << std::endl;
return 0;
这在 C++11 中是否合法,还是标记为 XXX
的行不明确?
同样的问题,但标记的行替换为:
std::vector<double> dest( convert( src ) );
或与
std::vector<double> dest;
dest = convert( src );
在 C++11 之前,我认为第二个是非法的,但其他两个 绝对不是。
FWIW:g++ (4.8.2) 接受第一个,但不接受其他两个(带有
-std=c++11
;否则它接受第一个和第三个,但是
不是第二个)。 VS 2013 接受所有这些,但 Intellisense
标记所有这些都是错误的(这就是触发我的
兴趣:滚动条上有一个漂亮的红色标记,带有
红色下划线的符号,但代码编译完美)。
换句话说:三种编译器,三种不同的行为。
(对于那些想知道为什么的人:这是一个标准的成语 用于获取上下文——作业的左侧, 例如——参与重载决议。)
【问题讨论】:
由于most vexing parse 问题,您的第一个替换std::vector<double> dest(convert(src));
可能不明确。
@CouchDeveloper 对于最棘手的解析问题,convert
必须命名一个类型。它没有。我认为对于任何具有多个可以用单个参数调用的构造函数的类型,第一个替换(第二个版本)是(并且一直是)模棱两可的。无论如何,Visual Studios 2013 接受它(并调用我想要的转换)。我正在寻找可以向我解释新的initializer_list
构造函数如何干预的人。一些解释 WRT rvalue-ref ctor 也会很好,尽管我想我理解它们。
我唯一能看到initializer_list
被使用的地方是在返回值中:你可以写ConvertProxy<Source>(source)
而不是source
,但那是因为你知道返回的类型。同样对于这个特殊问题,ConvertProxy 的意义何在,为什么不只使用一个带有 typename 目标的转换函数。
MSVC accepts the ambiguous conversions due to a non-standard extension.
如果你不介意使用自动,你可以实现更简单的事情。下面是代码:ideone.com/WietLn,它利用了 initializer_list、r 值引用和基于范围的循环。编辑:您可以更改对 r-value refs 的引用以返回 convert 但我没有看到任何需要。
【参考方案1】:
编译后的代码存在歧义,必须首先解决,我认为 C++11 标准并没有为此添加任何内容。
您应该使用容器的完整定义作为 模板模板 参数来匹配替换;最明确的是:
template <typename T, typename A, template <typename, typename> class Dest>
operator Dest<T, A>() const
return Dest<T, A>(m_source->begin(), m_source->end());
三种情况都编译,替换匹配
[T = double, A = allocator<double>, Dest = vector],
使用旧的 gcc-4.1.2 和闪亮的新 clang-3.4 - 带有和不带有 -std=c++11
标志。
【讨论】:
我不能使用模板模板,因为我必须支持的一些容器是非标准的,并且没有相同的模板参数,或者不是模板。 @JamesKanze 为什么你不能继续添加 operator() 定义来满足所有可能的匹配? 因为我事先不知道所有可能的匹配。尝试这样做或多或少违背了使用模板的目的。 (我经常使用这个成语,它针对三个或四个特定类型,在这种情况下,我会为每个类型手动重载,但这里不是这种情况。) @JamesKanze 我并不热衷于进行哲学讨论,但在这种情况下,将容器作为模板参数传递对我来说似乎是错误的选择。特别是如果您不愿意使用typename Dest
和 typename <typename typename> Dest
重载来适应编译器。我知道您的示例是一个玩具,但是-您使用开始/结束-您可以恢复为范围而不是容器吗?还是依靠 T::value_type 鸭子打字?
@JamesKanze 考虑将所有要求移到问题正文中。以上是关于在新的初始化序列存在的情况下,运算符重载决议如何工作?的主要内容,如果未能解决你的问题,请参考以下文章