繁体   English   中英

为什么我的显式构造函数会为我的转换运算符创建这种歧义?

[英]Why is my explicit constructor creating this ambiguity for my conversion operator?

我无法弄清楚为什么我的转换运算符正在考虑显式构造函数。

#include <utility>

template <typename T = void>
struct First
{
    template <typename... Targs>
    First(Targs&&... args) {}
};

template <>
struct First<void> {};

template <typename T>
struct Second
{
    template <typename... Targs>
    Second(Targs&&... args) {}
};

template <typename... T> class A;

template <typename SecondType>
class A<SecondType>
{
  public:
    A(const A&) = default;
    explicit A(const First<void>& first) {}
    explicit A(const Second<SecondType>& second) {}
};

template <typename FirstType, typename SecondType>
class A<FirstType, SecondType>
{
  public:
    A(const First<FirstType> & first) {}
    explicit operator A<SecondType>() const { return A<SecondType>(First<>()); }
};

int main() {
    A<int, float> a{First<int>(123)};
    A<float> b = static_cast<A<float>>(a);

    // test.cpp:41:41: error: call of overloaded ‘A(A<int, float>&)’ is ambiguous
    //    41 |     A<float> b = static_cast<A<float>>(a);
    //       |                                         ^
    // test.cpp:28:14: note: candidate: ‘A<SecondType>::A(const Second<SecondType>&) [with SecondType = float]’
    //    28 |     explicit A(const Second<SecondType>& second) {}
    //       |              ^
    // test.cpp:26:5: note: candidate: ‘constexpr A<SecondType>::A(const A<SecondType>&) [with SecondType = float]’
    //    26 |     A(const A&) = default;
    //       |     ^
    
    return 0;
}

如果我像这样直接调用运算符: A<float> b = a.operator A<float>(); 那么它工作正常,所以我想知道是否有一些关于 static_cast<> 用于调用我不知道的转换运算符的规则。 但是我发现很难理解的是,当我没有以任何方式显式调用它们时,为什么它甚至会考虑显式构造函数,据我所知。

我正在使用 g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 进行编译

虽然看起来是这样,

static_cast<A<float>>(a);

实际上并不首先尝试调用用户定义的转换function。 实际上,它的行为与想象中的声明相同

A<float> temp_obj(A);

其中temp_obj是创建的临时名称的发明名称。

作为结果,

A<float> b = static_cast<A<float>>(a);

是,除了可能额外的移动操作,与

A<float> b(a);

上面的形式是直接初始化

在直接初始化中,仅考虑目标 class 的构造函数。 考虑参数类型的用户定义转换函数。 在您的情况下,有两个可行的候选构造函数:

explicit A(const Second<SecondType>& second);

A(const A&);

(构造函数上的explicit对直接初始化不起作用。)

这两种方法都是可行的,并且都需要对参数进行一次用户定义的转换。 第一个参数通过Second<SecondType>的可变参数构造函数获得,第二个参数通过A<int, float>的用户定义转换 function 获得。

此时似乎不应考虑用户定义的转换 function,因为它是显式的,并且 function 参数的初始化是复制初始化,它不允许显式构造函数和转换函数,但有一个特定的例外由于CWG 问题 899的解决,复制/移动构造函数对此进行了处理。

这给我们留下了两个可行的构造函数,它们都具有同样好的转换序列。 结果构造是模棱两可的,编译器是正确的。

没有任何explicit的标记与此相关。 只有将Second<SecondType>的可变参数构造函数设为explicit才能解决歧义。


但是,如果您使用--std=c++17或更高版本,您将看到代码将在 Clang 和 GCC 中编译。

这可能是因为在 C++17 中引入了强制复制省略。 在许多情况下,现在必须在通常需要调用它们的地方省略复制/移动构造函数。

新规则实际上并不适用于我们上面调用的复制构造函数,但由于这可能只是标准中的一个疏忽,因此存在开放的CWG 问题 2327考虑复制省略是否也应适用于此直接初始化。

在我看来,编译器已经为直接初始化实现了这种额外的省略行为,并且以这样一种方式,它使省略的复制/移动构造函数候选者在重载决议中比需要用户定义的普通构造函数更好地匹配转换顺序。

这消除了歧义,并且仅调用了A<int, float>的用户定义的转换 function (使用省略的复制/移动构造函数A<float> )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM