Почему `` литерал `` поощряется к распаду `const char *` в совпадении типа аргумента С++?

Я играю с перегрузкой операторов в С++ 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 * предпочтительнее версия шаблона.

Вы можете это объяснить? В частности:

  • Почему const char* предпочтительнее const char (&)[N]?
  • Почему const char (&)[N] предпочтительнее const char (&)[] (не шаблон)?
  • Почему const char (&&)[N] невозможно выполнить компиляцию?
  • Есть ли "правильный путь" для записи литеральных строк?

Спасибо.

#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;
}

Edit:

В случае, если это имеет значение, я компилирую с помощью:

c++ --std=c++14 -Iinclude   -c -o main.o main.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

Ответ 1

Почему const char* предпочтительнее const char (&)[N]?

Причина этого довольно техническая. Несмотря на то, что распад строкового литерала от const char[N] до const char* является преобразованием, он попадает в категорию "преобразование значения lvalue" и поэтому считается [over.ics.rank]/3 таким же хорошим, как без преобразования вообще. Поскольку для любой перегрузки требуется "без преобразования", выигрывает не шаблонная перегрузка.

Почему const char (&)[N] предпочтительнее const char (&)[] (не шаблон)?

Невозможно привязать ссылку к массиву неизвестной привязки к значению массива типов известной привязки. Вместо этого ссылка на массив неизвестной границы может быть привязана только к значениям, которые сами являются массивами неизвестной границы.

Почему const char (&&)[N] невозможно выполнить компиляцию?

Строковый литерал - это lvalue, поэтому я не уверен, почему вы ожидаете, что это сработает.

Есть ли "правильный путь" для записи литеральных строк?

Вы можете использовать шаблон вспомогательной функции, который фиксирует свой аргумент с использованием ссылки пересылки, чтобы не уничтожать информацию о типе (const char* versus 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

Ответ 2

  • Перегрузка с использованием указателя предпочтительна, поскольку это не шаблон в соответствии с

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;
}

Обратите внимание, что предоставление другого шаблона, принимающего указатель, не будет работать, потому что оба специализаций шаблонов будут точными, и мы получим двусмысленный выбор перегруженных функций.