问题 为什么``literal``鼓励在C ++参数类型匹配中衰减为`const char *`?


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

也就是说,我试图看看我是否可以区分:

const char * run_time;

"compile time"

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

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

如果我更改模板版本以获取rvalue-reference(&&)参数 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

10575
2018-05-02 07:10


起源

我的猜测是它被选中,因为它是非模板。非模板始终是优先重载。 - W.F.
是的,非模板重载比模板重载更受欢迎 - bolov
另一方面,rvalue问题对我来说非常有意义,迫不及待地想看到完整答案...... - W.F.
@bolov:只有在其他方面同样好的时候 - MSalters
IIRC const char (&)[] 无法绑定到类型的值 const char [N] - cpplearner


答案:


为什么是 const char* 首选 const char (&)[N]

其原因颇具技术性。即使字符串文字的衰减 const char[N] 至 const char* 是转换,它属于“左值变换”类别,因此被[over.ics.rank] / 3视为完全没有转换。由于任何过载都需要“无转换”,因此非模板过载获胜。

为什么是 const char (&)[N] 首选 const char (&)[] (非模板)?

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

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

字符串文字是一个左值,所以我不确定为什么你会期望这个工作。

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

您可以使用辅助函数模板,使用转发引用捕获其参数,以便不破坏任何类型信息(const char* 与 const char[N])然后使用模板专业化对类型进行调度。您可能还想使用SFINAE来确保它被禁用,除非是 const char* 要么 const char[N] 传入。就是说,

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


9
2018-05-02 07:51



你还没见过 我的论文。 :P我正在修改,但它通过了Evolution。 - Columbo
@Columbo我很惊讶,为什么你没有在C中提到过, int[] 甚至兼容 int[N],他们的复合类型是后者?这是允许它的非常好的动力,IMO。 - Johannes Schaub - litb
@Columbo是故意选择的 char 变种? void f(char(&&)[]); void f(int(&&)[2]); f({10});。直观地说,我本以为它会选择 int 变种。 “N大于元素数量”-rule IMO应该是一个打破平局 - 正如你在最后一段中为“未知”与“已知”规则制定的那样,并且IMO不应该忽略正在进行的其他类型调整。或者我在这里误解了什么? - Johannes Schaub - litb
请注意,您引用的DR1307要求两个阵列基本类型相同。因此,它不会将类型调整视为比较大的数组大小更糟糕。 - Johannes Schaub - litb
@ JohannesSchaub-litb这确实是有意的。我要检查是否在核心讨论中提到过;我想是的。 - Columbo


  1. 引用指针的重载是优先的,因为根据它不是模板

13.3.3最佳可行功能[over.match.best]

...

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

...

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

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

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

  3. 你可以提供另一个模板来引用指针:
template< typename = void > void
foo(char const * & text)
{
    ::std::cout << "got ptr" << ::std::endl;
}

请注意,提供另一个采用指针的模板将不起作用,因为两个模板特化都很好,我们将选择过多的函数。


1
2018-05-02 07:47