Why is `“literal”` encouraged to decay to `const char*` in C++ argument type match?

老子叫甜甜 提交于 2019-12-24 07:56:58

问题


I'm playing around with overloading operators in c++14, and I tried to match two types of arguments: any-old-const-char*, and a-string-literal.

That is, I'm trying to see if I can discriminate between:

const char * run_time;

and

"compile time"

I wrote the code below, and as shown, when I try span >> "literal" it invoked the const char* function.

When I #if 0-out the const char* version, the template version gets called just fine.

If I change the template version to take an rvalue-reference (&&) parameter for literal, it doesn't compile.

If I add a const char (&literal)[] non-template version, the const char* version is still preferred. Removing the const-char* version, the template version is preferred.

Can you explain this? In particular:

  1. Why is const char* preferred over const char (&)[N]?
  2. Why is const char (&)[N] preferred over const char (&)[] (non-template)?
  3. Why is const char (&&)[N] unable to compile?
  4. Is there a "right way" to capture literal strings?

Thanks.

#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:

In case it matters, I'm compiling with:

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

And c++ says:

$ 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:


Why is const char* preferred over const char (&)[N]?

The reason for this is rather technical. Even though the decay of a string literal from const char[N] to const char* is a conversion, it falls into the "lvalue transformation" category and is therefore considered by [over.ics.rank]/3 to be as good as no conversion at all. Since "no conversion" is required for either overload, the non-template overload wins.

Why is const char (&)[N] preferred over const char (&)[] (non-template)?

It is not possible to bind a reference to array of unknown bound to a value of type array of known bound. Instead, a reference to array of unknown bound can only be bound to values that are themselves arrays of unknown bound.

Why is const char (&&)[N] unable to compile?

A string literal is an lvalue so I'm not sure why you would expect this to work.

Is there a "right way" to capture literal strings?

You can use a helper function template that captures its argument using a forwarding reference so as to not destroy any type information (const char* versus const char[N]) then dispatch on the type using template specialization. You'll probably also want to use SFINAE to make sure it is disabled if anything other than a const char* or const char[N] is passed in. To wit,

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 link: http://coliru.stacked-crooked.com/a/0e9681868d715e87




回答2:


  1. The overload taking a pointer is preffered because it is not a template according to

13.3.3 Best viable function [over.match.best]

...

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

...

(1.7) F1 is not a function template specialization and F2 is a function template specialization

  1. actually non-template const char (&)[] does not seem to compile at all because it is a reference to a non-bound array. It is possible to pass a pointer like this const char [], but not array.

  2. this should fail at least for the same reason as (2)

  3. you can provide another template taking a reference to pointer:
template< typename = void > void
foo(char const * & text)
{
    ::std::cout << "got ptr" << ::std::endl;
}

Note that providing another template taking a pointer won't work because both template specializations will be fine and we'll get ambiguous choice of overloaded functions.



来源:https://stackoverflow.com/questions/45541997/overloaded-function-of-parameters-reference-to-const-char-array-and-const-char-p

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!