在新的初始化序列存在的情况下,运算符重载决议如何工作?

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&lt;double&gt; dest(convert(src)); 可能不明确。 @CouchDeveloper 对于最棘手的解析问题,convert 必须命名一个类型。它没有。我认为对于任何具有多个可以用单个参数调用的构造函数的类型,第一个替换(第二个版本)是(并且一直是)模棱两可的。无论如何,Visual Studios 2013 接受它(并调用我想要的转换)。我正在寻找可以向我解释新的initializer_list 构造函数如何干预的人。一些解释 WRT rvalue-ref ctor 也会很好,尽管我想我理解它们。 我唯一能看到initializer_list被使用的地方是在返回值中:你可以写ConvertProxy&lt;Source&gt;(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 Desttypename &lt;typename typename&gt; Dest 重载来适应编译器。我知道您的示例是一个玩具,但是-您使用开始/结束-您可以恢复为范围而不是容器吗?还是依靠 T::value_type 鸭子打字? @JamesKanze 考虑将所有要求移到问题正文中。

以上是关于在新的初始化序列存在的情况下,运算符重载决议如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

在这种情况下如何重载“+”运算符,

在Singleton类的情况下我应该如何编写复制构造函数?如何重载=运算符?

C++ 模板函数重载决议

扩展函数和运算符重载

C++学习26 运算符重载的概念和语法

Kotlin 扩展函数和运算符重载[第一行代码 Kotlin 学习笔记]