【问题标题】:Unexpected overload resolution in visual studio involving void*, string and const char[]Visual Studio 中涉及 void*、string 和 const char[] 的意外重载解决方案
【发布时间】:2014-04-07 05:13:52
【问题描述】:

我在 Visual Studio 编译器(在 VS2010 和 VS2012 中测试)出现了意外的重载解析行为。

小例子:

#include <iostream>
#include <string>

void f(void *)
{
    std::cout << "f(void*)\n";
}

void f(const std::string &)
{
    std::cout << "f(const std::string &)\n";
}

int main()
{
    f("Hello World!");
}

输出:

> f(void *)

预期输出:

> f(const std::string &)

使用 GCC 编译(使用 4.6.3 测试)会生成预期的输出。

如果我注释掉 f() 的“const std::string &”版本,Visual Studio 会愉快地在 /W4 上编译而没有任何警告,而 GCC 会发出以下错误(如预期的那样):“来自 'const void 的无效转换*' 到 'void*' [-fpermissive]"。

有谁知道为什么 Visual Studio 会以这种方式运行,基本上选择 const 强制转换重载而不是转换为 char[] 的 std::string?

有没有办法禁止这种行为,或者至少让VS产生警告?

【问题讨论】:

    标签: c++


    【解决方案1】:

    我不明白为什么会出乎意料。 char const[]std::string 的转换涉及用户定义的转换; 转换为void* 不会。以及涉及的转换 用户定义的转换总是“不如” 不涉及用户定义的转换。

    这里真正的问题是 C++ 没有内置字符串 类型,并且字符串文字没有类型std::string。 正常的解决方案是为char const* 提供重载 还有:

    void f( void* );
    void f( std::string const& );
    inline void f( char const* p ) { f( std::string( p ) ); }
    

    这个额外的重载将拾取字符串文字。

    (作为一般规则:任何时候您超载:如果其中一个 重载用于std::string,为char const* 提供一个作为 好吧,如果有任何用于算术类型,请提供一个 int,捕捉整型文字,如果有的话,用于浮动 点类型,为double提供一个,捕捉浮点数 字面量。)

    【讨论】:

    • +1(特别是对于一般规则),但我可以明白为什么它出乎意料;因为它还依赖于不幸的 const-dropping 从字符串文字到 char* 的转换。
    • 问题中的问题是转换为指向非常量的指针,这是不允许的。
    • @Angew 但是 const-dropping 仍然不是用户定义的转换。 (在更一般的情况下,我可以理解为什么它是意外的,因为从逻辑上讲,字符串文字和std::string 是字符串,并且人们认为没有必要进行转换。唉,出于历史原因......)跨度>
    • @JamesKanze 我知道,但即使有人知道字符串文字不是 std::strings,MSVC 在没有警告的情况下应用 const-drop 仍然令人困惑。
    • @interjay 我的解读是它不应该被允许:“只有当有明确的适当指针目标类型时才考虑这种转换,[...]”(C++03,§ 4.2/2)。我会将“适当的指针目标类型”解释为表示窄字符串文字的char*(并且只有char*)。无论如何,C++11 完全禁止它,即使是 char*
    【解决方案2】:

    对于 VS 2013 Microsoft documents silently dropping const for string literals 作为 C++ 的 Microsoft 特定行为:

    微软特定

    在 Visual C++ 中,您可以使用字符串文字来初始化指向 非常量 char 或 wchar_t。这在 C 代码中是允许的,但是 在 C++98 中弃用并在 C++11 中删除。

    ...

    当您设置 /Zc:strictStrings(禁用字符串文字类型转换)编译器选项时,当字符串文字转换为非 const 字符时,您可能会导致编译器发出错误。

    对于早于 VS 2013 的版本(例如 VS 2012's documentation),Microsoft 将 C++ 中的字符串文字记录为使用 char 的非常量数组的 C 约定。

    【讨论】:

      【解决方案3】:

      正如其他人所指出的,明显的问题是,MSVC 允许从字符串文字隐式转换为非constchar*,然后再转换为void*

      我说很明显,因为您的 void* 重载应该是 void const* 重载,因为它不会更改指向的数据。这样做会使事情变得“更糟”,因为使用字符串文字调用它现在将明确选择 void const* 重载。然而,这说明出了什么问题:"" 是一个char const(&amp;)[1]const char 的数组),而不是std::string,并且char const(&amp;)[1] 与指针的关系比与std::string 的关系更密切。依靠重载选择 std::string 在指针上是脆弱的,即使在 gcc 上也是如此,因为使您的代码 const 正确会破坏它!

      为了解决这个问题,我们可以为std::string 编写一个贪心重载。

      template<typename S, typename=typename std::enable_if<std::is_convertible<S,std::string>::value>::type>
      void f(S&&s){
        f(std::string{std::forward<S>(s)});
      }
      

      上述两个重载保持不变(添加const 除外)。

      或者(更好)通过标签调度:

      void f(void const* v, std::false_type){
        std::cout << "f(void*)\n";
      }
      void f(std::string const& s, std::true_type){
        std::cout << "f(const std::string &)\n";
      }
      template<typename T>
      void f(T&&t){
        return f(std::forward<T>(t), std::is_convertible<T,std::string>() );
      }
      

      这两种都是手动函数重载调度的方法,偏向std::string

      live example

      请注意,std::string 文字现在可以在 C++ 中使用,但我建议不要因为脆弱而要求它们。

      【讨论】:

      • @dyp 好点。那么在程序员的意图方面模棱两可吗?
      • 是的,也许。这要么是一个非常微妙的重载解决方案(因此容易出错),要么没有表达程序员的意图。
      • 顺便说一句。 coid f(std::string const&amp; s, std::true_type){ blah } 中还有一个错字(要我改正这些错字吗?)
      • @DyP 当然,但我真的应该编译我的答案。 :) 我需要找到一种在手机上设置编译器的好方法,或者在手机上运行良好的 Web 编译器(其中许多都有古怪的编辑框,不能很好地与触摸屏浏览器配合使用)。跨度>
      最近更新 更多