gpt4 book ai didi

c++ - Clang vs GCC vs MSVC 模板转换运算符——哪个编译器是对的?

转载 作者:行者123 更新时间:2023-12-02 15:17:48 29 4
gpt4 key购买 nike

我有带有转换运算符的简单代码,似乎所有编译器都给出了不同的结果,很好奇哪个编译器(如果有)是正确的?
我也尝试了不同的组合,但下面的组合是最有趣的。代码是使用 C++11 标志编译的,但在 C++03 中也可能观察到相同的行为。

#include <iostream>

struct call_operator {
template<typename T>
operator T() {
std::cout << __FUNCTION__ << std::endl;
return {};
}

template<typename T>
operator const T&() const {
std::cout << __FUNCTION__ << std::endl;
static T t;
return t;
}

template<typename T>
operator T&() const {
std::cout << __FUNCTION__ << std::endl;
static T t;
return t;
}
};

int main() {
(void)static_cast<int>(call_operator());
(void)static_cast<const int&>(call_operator());
(void)static_cast<int&>(call_operator());
}

clang-3.6:
operator int
operator const int &
operator int &

g++-4.9:
operator T
operator const T&
operator T&

msvc 2014 CTP:
call_operator.cpp(17): error C2440: 'static_cast': cannot convert from 'call_operator' to ' const int &'

移除后:
template<typename T>
operator T();

msvc 编译:
call_operator::operator const int &
call_operator::operator const int &
call_operator::operator int &

此外,在去除 const in 后
template<typename T>
operator const T&();

clang-3.6:
call_operator.cpp:26:9: error: ambiguous conversion for static_cast from 'call_operator' to 'int' (void)static_cast<int>(call_operator());

g++-4.9:
operator T
operator const T&
operator T&

msvc 2014 CTP:
call_operator.cpp(16): error C2440: 'static_cast': cannot convert from 'call_operator' to 'int'

最佳答案

总之 :Clang 是正确的(尽管在一种情况下,出于错误的原因)。 GCC 在第二种情况下是错误的。 MSVC 在第一种情况下是错误的。

让我们从static_cast开始(§5.2.9 [expr.static.cast]/p4,所有引用均来自 N3936):

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.



相应地,三个 static_cast这里实际上是三个初始化:
int t1(call_operator{});
const int & t2(call_operator{});
int & t3(call_operator{});

请注意,我们重写了 call_operator()call_operator{}仅用于说明目的,如 int t1(call_operator());是最烦人的解析。这两种初始化形式之间存在小的语义差异,但这种差异对本次讨论无关紧要。
int t1(call_operator{});
此初始化的适用规则在 §8.5 [dcl.init]/p17 中列出:

if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.



我们继续第 13.3.1.5 节 [over.match.conv],其中说:

Assuming that “cv1 T” is the type of the object being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

  • The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (13.3.3.1.1) are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T with a qualification conversion (4.4) are also candidate functions. Conversion functions that return a cv-qualified type are considered to yield the cv-unqualified version of that type for this process of selecting candidate functions. Conversion functions that return “reference to cv2 X” return lvalues or xvalues, depending on the type of reference, of type “cv2 X” and are therefore considered to yield X for this process of selecting candidate functions.

2 The argument list has one argument, which is the initializer expression. [ Note: This argument will be compared against the implicit object parameter of the conversion functions. —end note ]



模板参数推导后的候选集为:
operator T() - with T = int
operator const T& () const - with T = int
operator T&() const - with T = int

参数列表由单个表达式 call_operator{} 组成,这是非常量。因此,它更好地转换为 operator T() 的非常量隐式对象参数。比其他两个。因此, operator T()是最佳匹配,由重载决议选择。
const int & t2(call_operator{});
此初始化由 §8.5.3 [dcl.init.ref]/p5 控制:

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

  • If the reference is an lvalue reference and the initializer expression

    • is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2,” or
    • has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “cv3 T3,” where “cv1 T1” is reference-compatible with “cv3 T3” (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)).

then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).



请注意,此步骤仅考虑返回左值引用的转换函数。

Clang 似乎将候选集推断为*:
operator const T& () const - with T = int
operator T&() const - with T = int

很明显,这两个函数都依赖于隐式对象参数,因为它们都是 const .此外,由于两者都是直接引用绑定(bind),根据 §13.3.3.1.4 [ics.ref]/p1,从任一函数的返回类型到 const int & 所需的转换是身份转换。 ( 不是 资格调整 - 指的是 §4.4 [conv.qual] 中描述的转换,仅适用于指针。)

但是,似乎 Clang 对 operator T&() 执行的推导在这种情况下是不正确的‡。 §14.8.2.3 [temp.deduct.conv]/p5-6:

5 In general, the deduction process attempts to find template argument values that will make the deduced A identical to A. However, there are two cases that allow a difference:

  • If the original A is a reference type, A can be more cv-qualified than the deduced A (i.e., the type referred to by the reference)
  • The deduced A can be another pointer or pointer to member type that can be converted to A via a qualification conversion.

6 These alternatives are considered only if type deduction would otherwise fail. If they yield more than one possible deduced A, the type deduction fails.



由于类型推导可以通过推导 T 成功如 const intoperator T&()对于推导类型和目标类型之间的精确匹配,不应考虑替代方案, T应该被推断为 const int ,而候选集实际上是
operator const T& () const - with T = int
operator T&() const - with T = const int

再一次,来自结果的两个标准转换序列都是身份转换。 GCC(和 EDG,感谢 @Jonathan Wakely 进行测试)正确推断 Toperator T&()成为 const int在这种情况下*。

然而,无论推论是否正确,这里的决胜局都是一样的。因为,根据函数模板的偏序规则, operator const T& ()operator T&()更专业(由于 §14.8.2.4 [temp.deduct.partial]/p9 中的特殊规则),前者在 §13.3.3 [over.match.best]/p1 中以决胜局获胜,第二个列表,最后一个要点:

F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.



因此,在这种情况下,Clang 得到了正确的结果,但是(部分)错误的原因。 GCC 以正确的理由得到正确的结果。
int & t3(call_operator{});
这里没有战斗。 operator const T&();根本不可能用于初始化 int & .只有一个可行的功能, operator T&()T = int ,所以它是最好的可行函数。

如果 operator const T&();怎么办不是 const ?

这里唯一有趣的情况是初始化 int t1(call_operator{}); .两个强有力的竞争者是:
operator T() - with T = int
operator const T& () - with T = int

请注意,关于对标准转换序列进行排名的规则 - §13.3.3 [over.match.best]/p1,第二个列表,第二个要点:

the context is an initialization by user-defined conversion (see 8.5, 13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type.



和 §13.3.3.2 [over.ics.rank]/p2:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

  • S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence)


无法区分这两者,因为需要进行转换才能获得 int来自 const int &是左值到右值的转换,也就是左值转换。排除左值转换后,从结果到目的类型的标准转换顺序是相同的; §13.3.3.2 [over.ics.rank] 中的任何其他规则也不适用。

因此,可能区分这两种功能的唯一规则再次是“更特化”的规则。那么问题是是否是 operator T() 之一和 operator const T&()比另一个更专业。答案是不。详细的偏序规则相当复杂,但在§14.5.6.2 [temp.func.order]/p2 中的示例中很容易找到类似的情况,它标记了对 g(x) 的调用。由于含糊不清:

template<class T> void g(T);
template<class T> void g(T&);


快速阅读第 14.8.2.4 节 [temp.deduct.partial] 中指定的程序可以确认给定的模板采用 const T&另一个拿了 T按值(value)来看,两者都不比另一个更专业**。因此,在这种情况下,没有唯一的最佳可行函数,转换不明确,代码格式错误。 †

* Clang 和 GCC 为 operator T&() 推导出的类型case 是通过使用 operator const T&() 运行代码来确定的。移除。

** 简而言之,在偏序的推导过程中,在进行任何比较之前,引用类型被所引用的类型替换,然后顶级 cv 限定符被剥离,因此 const T&T产生相同的签名。然而,第 14.8.2.4 节 [temp.deduct.partial]/p9 包含一个特殊的规则,当有问题的两种类型都是引用类型时,这使得 operator const T&()operator T&()更专业;当其中一种类型不是引用类型时,该规则不适用。

† GCC 似乎没有考虑 operator const T&()这种情况下可行的转换,但确实考虑了 operator T&()一个可行的转换。

‡ 这似乎是 Clang bug 20783 .

关于c++ - Clang vs GCC vs MSVC 模板转换运算符——哪个编译器是对的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25745759/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com