【问题标题】:Why is `"literal"` encouraged to decay to `const char*` in C++ argument type match?为什么在 C++ 参数类型匹配中鼓励“literal”衰减为 const char*?
【发布时间】:2017-05-02 07:10:33
【问题描述】:

我正在使用 c++14 中的重载运算符,我尝试匹配两种类型的参数:any-old-const-char* 和 a-string-literal。

也就是说,我正在尝试看看我是否可以区分:

const char * run_time;

"compile time"

我编写了下面的代码,如图所示,当我尝试span >> "literal" 时,它调用了const char* 函数。

当我#if 0-out the const char* 版本时,模板版本被调用就好了。

如果我将模板版本更改为采用literal 的右值引用 (&&) 参数,它不会编译。

如果我添加const char (&literal)[] 非模板版本,const char* 版本仍然是首选。去掉 const-char* 版本,首选模板版本。

你能解释一下吗?特别是:

  1. 为什么const char*const char (&)[N] 更受欢迎?
  2. 为什么const char (&)[N] 优于const char (&)[](非模板)?
  3. 为什么const char (&&)[N]无法编译?
  4. 是否有“正确的方法”来捕获文字字符串?

谢谢。

#include <iostream>
using namespace std;

#include <gsl/gsl>
#include <type_name.h++>

template<unsigned N>
auto
operator>>(gsl::span<const char*,-1>& spn, const char (&literal)[N])
    -> gsl::span<const char*, -1>&
{
    cout << "Got array: " << literal << endl;
    return spn;
}

auto
operator>>(gsl::span<const char*,-1>& spn, const char *literal)
    -> gsl::span<const char*, -1>&
{
    cout << "Got const-char*: " << literal << endl;
    return spn;
}
#if 0
#endif

int
main(int argc, const char *argv[])
{
    auto spn = gsl::span<const char*>(argv, argc);

    cout << type_name<decltype(spn)>() << endl; // gsl::span<const char *, -1>

    cout << type_name<decltype("literal")>() << endl; // char const (&)[8]

    cout << type_name<decltype(("literal"))>() << endl; // char const (&)[8]

    auto helpx = "literal";
    cout << type_name<decltype(helpx)>() << endl; // const char *


    spn >> "literal"; // Got const-char*: literal

    return 0;
}

编辑:

如果它很重要,我正在编译:

c++ --std=c++14 -Iinclude   -c -o main.o main.c++

而 c++ 说:

$ c++ --version
Apple LLVM version 8.0.0 (clang-800.0.42.1)
Target: x86_64-apple-darwin16.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

【问题讨论】:

  • 我的猜测是选择它是因为它不是模板。非模板总是优先重载。
  • 是的,非模板重载优于模板重载
  • 另一方面,右值问题对我来说非常有趣,迫不及待想看到完整的答案......
  • @bolov: 除非其他方面同样好
  • IIRC const char (&amp;)[] 无法绑定到 const char [N] 类型的值

标签: c++ c++14


【解决方案1】:

为什么const char*const char (&amp;)[N] 更受欢迎?

这样做的原因是相当技术性的。尽管字符串文字从const char[N] 衰减到const char* 是一种转换,但它属于“左值转换”类别,因此[over.ics.rank]/3 认为与没有转换一样好一点也不。由于任一重载都需要“不转换”,因此非模板重载获胜。

为什么const char (&amp;)[N] 优于const char (&amp;)[](非模板)?

不可能将对未知边界数组的引用绑定到已知边界数组类型的值。相反,对未知边界数组的引用只能绑定到本身就是未知边界数组的值。

为什么const char (&amp;&amp;)[N]无法编译?

字符串文字是一个左值,所以我不知道为什么你会期望它起作用。

是否有“正确的方法”来捕获文字字符串?

您可以使用辅助函数模板,该模板使用转发引用捕获其参数,以免破坏任何类型信息(const char*const char[N]),然后使用模板特化在类型上分派。如果传入 const char*const char[N] 以外的任何内容,您可能还想使用 SFINAE 确保它被禁用。也就是说,

template <bool b>
struct f_helper;

template <>
struct f_helper<true> {
    void do_it(const char*) {
        puts("pointer");
    }
};

template <>
struct f_helper<false> {
    template <std::size_t N>
    void do_it(const char (&)[N]) {
        printf("array of length %zd\n", N);
    }
};

template <class T, class = typename std::enable_if<std::is_same<char*, std::decay_t<T>>::value ||
                                                   std::is_same<const char*, std::decay_t<T>>::value>::type>
void f(T&& s) {
    f_helper<std::is_pointer<std::remove_reference_t<T>>::value>{}.do_it(s);
}

Coliru 链接:http://coliru.stacked-crooked.com/a/0e9681868d715e87

【讨论】:

  • 你还没有看到my paper。 :P 我目前正在修改,但它通过了 Evolution。
  • @Columbo 我很惊讶,为什么你没有提到在 C 中,int[] 甚至与 int[N] 兼容,它们的复合类型是后者? IMO,这是一个很好的动机。
  • @Columbo 是有意选择char 变体吗? void f(char(&amp;&amp;)[]); void f(int(&amp;&amp;)[2]); f({10});。直觉上,我本以为它会选择int 变体。 “N 大于元素的数量” - 规则 IMO 应该是一个平局 - 正如您在最后一段中为“未知”与“已知”规则制定的那样,并且 IMO 不应该忽略正在进行的其他类型调整.还是我在这里误解了什么?
  • 请注意,您引用的 DR1307 要求两个数组基本类型相同。所以它不会认为类型调整比更大的数组大小更糟糕。
  • @JohannesSchaub-litb 这确实是故意的。我必须检查它是否在核心讨论中被提及;我想是的。
【解决方案2】:
  1. 采用指针的重载是首选,因为它不是根据
  2. 的模板

13.3.3 最佳可行函数[over.match.best]

鉴于这些定义,一个可行函数 F1 被定义为比另一个可行函数更好的函数 F2 如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列,则

...

(1.7) F1 不是函数模板特化,F2 是函数模板特化

  1. 实际上非模板const char (&amp;)[] 似乎根本无法编译,因为它是对非绑定数组的引用。可以传递像 const char [] 这样的指针,但不能传递数组。

  2. 至少由于与 (2) 相同的原因,这应该失败

  3. 你可以提供另一个模板来引用指针:

template< typename = void > void
foo(char const * & text)
{
    ::std::cout << "got ptr" << ::std::endl;
}

请注意,提供另一个带有指针的模板是行不通的,因为两种模板特化都可以,而且我们会在重载函数的选择上模棱两可。

【讨论】:

    猜你喜欢
    • 2014-08-24
    • 2012-10-03
    • 2011-08-14
    • 2015-02-03
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 2023-04-02
    • 2016-05-24
    相关资源
    最近更新 更多