static_cast: 转换函数模板 - 它们真的有效吗?

7

据我所读的static_cast,以下代码应该能够工作:

#include <iostream>
#include <string>

class ConvSample
{
public:
    template<typename T>
    constexpr operator T(){
        return {};
    }
};

int main()
{
    ConvSample aInst;

    int i = aInst;
    std::cout << i << "\n";

    std::string str = static_cast<std::string>(aInst);
    std::cout << str << "\n";

    return 0;
}

它在一些编译器如Clang中完美运行。但是例如在MSVC或ICC中不完美运行。

请参见Compiler Explorer

总的来说,它们抱怨由std :: string提供的不太有效的构造函数引起的一些歧义。

此外,如果我在gcc上打开Wconversion,则会导致分段错误?!

代码有问题吗?这些错误只是编译器的错误吗? 如果我更改代码以不使用模板,则它可以正常工作: Compiler Explorer


FYI,所有的编译都使用static_cast<const basic_string&>,这是构造函数(7)的精确匹配:https://en.cppreference.com/w/cpp/string/basic_string/basic_string - Richard Critten
@RichardCritten:但这将涉及在转换函数中使用临时引用的未定义行为。 - Davis Herring
在您提到的情况下,它会被隐式转换为std::string。在这个隐式转换之后,会调用复制构造函数...... 但那显然不是我想要的。它应该只调用转换运算符(它可以在没有static_cast或c风格强制转换的情况下正常工作)。 - Bernd
1个回答

2
很不幸,标准在这里表述得模糊([expr.static.cast]/4,省略引用):
一个表达式e可以被显式转换为类型T,如果有从eT的隐式转换序列,或者如果从e到类型为T的对象或引用的直接初始化的重载决议会找到至少一个可行的函数。[...]结果对象是从e进行直接初始化的。
这两个启用条件都成立:有一个隐式转换序列(由所需的转换函数调用组成),以及几个可行的直接初始化函数(因为各种单参数的std::string构造函数也有隐式转换序列)。
然而,只有隐式转换序列的复制初始化拒绝将ConvSample转换为(例如)const char*,然后再转换为std::string时提供了一种明确的生成std::string的方法:它特别寻找到目标类型的转换函数,并且虽然它允许对(const std :: string&)进行转换,但通常实现不会将其解释为该转换函数模板也应该为该类型实例化并变得模棱两可。
最终调用的直接初始化在std::string的5个单参数构造函数(对于类似std::string_view的类型为6个)之间是模棱两可的:ConvSample当然可以以相同的“代价”转换为任何一个参数类型。
接受这一点的编译器正在应用复制初始化规则(但仍允许显式转换)。拒绝它的编译器正在应用直接初始化,我认为这正是目前的措辞要求的。针对隐式转换序列的引用只在C++17中引入了CWG242,而显然在这个领域实现分歧仍然存在。

1
我认为static_cast解释的问题并不能解释实现分歧。通过直接初始化std::string str(aInst);可以得到完全相同的结果。GCC和Clang是否错误地将复制初始化规则应用于直接初始化?也许是,但我很少看到它们都有同样的错误。 - Brian Bi
2
@Brian:这是一个很好的观点。如果“true”直接初始化表现出相同的行为,那么static_cast就应该做到这一点。通过尝试不同的explicit和参数类型组合,我已经发现了GCC/Clang之间的区别。如果我完善这个理论,我肯定会在这里编辑。 - Davis Herring
@Brian:这是CWG2327,它还涉及到新的C++17措辞。 - Davis Herring

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接